From 4a89c1397e78765e291932b11ad32dce882cef30 Mon Sep 17 00:00:00 2001 From: Jakub Walusiak Date: Mon, 22 Aug 2022 13:37:51 +0200 Subject: [PATCH 001/130] [in_app_purchases_android_platform] Add BillingClient reconnecting logic --- .../src/in_app_purchase_android_platform.dart | 136 +++++++++--------- 1 file changed, 64 insertions(+), 72 deletions(-) diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform.dart index 14dd69364497..31ab72e80b67 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform.dart @@ -30,17 +30,14 @@ const String kIAPSource = 'google_play'; class InAppPurchaseAndroidPlatform extends InAppPurchasePlatform { InAppPurchaseAndroidPlatform._() { billingClient = BillingClient((PurchasesResultWrapper resultWrapper) async { - _purchaseUpdatedController - .add(await _getPurchaseDetailsFromResult(resultWrapper)); + _purchaseUpdatedController.add(await _getPurchaseDetailsFromResult(resultWrapper)); }); // Register [InAppPurchaseAndroidPlatformAddition]. - InAppPurchasePlatformAddition.instance = - InAppPurchaseAndroidPlatformAddition(billingClient); + InAppPurchasePlatformAddition.instance = InAppPurchaseAndroidPlatformAddition(billingClient); - _readyFuture = _connect(); - _purchaseUpdatedController = - StreamController>.broadcast(); + _connect(); + _purchaseUpdatedController = StreamController>.broadcast(); } /// Registers this class as the default instance of [InAppPurchasePlatform]. @@ -50,12 +47,10 @@ class InAppPurchaseAndroidPlatform extends InAppPurchasePlatform { InAppPurchasePlatform.instance = InAppPurchaseAndroidPlatform._(); } - static late StreamController> - _purchaseUpdatedController; + static late StreamController> _purchaseUpdatedController; @override - Stream> get purchaseStream => - _purchaseUpdatedController.stream; + Stream> get purchaseStream => _purchaseUpdatedController.stream; /// The [BillingClient] that's abstracted by [GooglePlayConnection]. /// @@ -63,6 +58,7 @@ class InAppPurchaseAndroidPlatform extends InAppPurchasePlatform { @visibleForTesting late final BillingClient billingClient; + bool _isInitializing = true; late Future _readyFuture; static final Set _productIdsToConsume = {}; @@ -73,44 +69,35 @@ class InAppPurchaseAndroidPlatform extends InAppPurchasePlatform { } @override - Future queryProductDetails( - Set identifiers) async { + Future queryProductDetails(Set identifiers) async { List responses; PlatformException? exception; try { responses = await Future.wait(>[ - billingClient.querySkuDetails( - skuType: SkuType.inapp, skusList: identifiers.toList()), - billingClient.querySkuDetails( - skuType: SkuType.subs, skusList: identifiers.toList()) + billingClient.querySkuDetails(skuType: SkuType.inapp, skusList: identifiers.toList()), + billingClient.querySkuDetails(skuType: SkuType.subs, skusList: identifiers.toList()) ]); } on PlatformException catch (e) { exception = e; responses = [ // ignore: invalid_use_of_visible_for_testing_member SkuDetailsResponseWrapper( - billingResult: BillingResultWrapper( - responseCode: BillingResponse.error, debugMessage: e.code), + billingResult: BillingResultWrapper(responseCode: BillingResponse.error, debugMessage: e.code), skuDetailsList: const []), // ignore: invalid_use_of_visible_for_testing_member SkuDetailsResponseWrapper( - billingResult: BillingResultWrapper( - responseCode: BillingResponse.error, debugMessage: e.code), + billingResult: BillingResultWrapper(responseCode: BillingResponse.error, debugMessage: e.code), skuDetailsList: const []) ]; } - final List productDetailsList = - responses.expand((SkuDetailsResponseWrapper response) { + final List productDetailsList = responses.expand((SkuDetailsResponseWrapper response) { return response.skuDetailsList; }).map((SkuDetailsWrapper skuDetailWrapper) { return GooglePlayProductDetails.fromSkuDetails(skuDetailWrapper); }).toList(); - final Set successIDS = productDetailsList - .map((ProductDetails productDetails) => productDetails.id) - .toSet(); - final List notFoundIDS = - identifiers.difference(successIDS).toList(); + final Set successIDS = productDetailsList.map((ProductDetails productDetails) => productDetails.id).toSet(); + final List notFoundIDS = identifiers.difference(successIDS).toList(); return ProductDetailsResponse( productDetails: productDetailsList, notFoundIDs: notFoundIDS, @@ -131,20 +118,19 @@ class InAppPurchaseAndroidPlatform extends InAppPurchasePlatform { changeSubscriptionParam = purchaseParam.changeSubscriptionParam; } - final BillingResultWrapper billingResultWrapper = - await billingClient.launchBillingFlow( - sku: purchaseParam.productDetails.id, - accountId: purchaseParam.applicationUserName, - oldSku: changeSubscriptionParam?.oldPurchaseDetails.productID, - purchaseToken: changeSubscriptionParam - ?.oldPurchaseDetails.verificationData.serverVerificationData, - prorationMode: changeSubscriptionParam?.prorationMode); + final BillingResultWrapper billingResultWrapper = await _wrap( + () => billingClient.launchBillingFlow( + sku: purchaseParam.productDetails.id, + accountId: purchaseParam.applicationUserName, + oldSku: changeSubscriptionParam?.oldPurchaseDetails.productID, + purchaseToken: changeSubscriptionParam?.oldPurchaseDetails.verificationData.serverVerificationData, + prorationMode: changeSubscriptionParam?.prorationMode), + ); return billingResultWrapper.responseCode == BillingResponse.ok; } @override - Future buyConsumable( - {required PurchaseParam purchaseParam, bool autoConsume = true}) { + Future buyConsumable({required PurchaseParam purchaseParam, bool autoConsume = true}) { if (autoConsume) { _productIdsToConsume.add(purchaseParam.productDetails.id); } @@ -152,27 +138,23 @@ class InAppPurchaseAndroidPlatform extends InAppPurchasePlatform { } @override - Future completePurchase( - PurchaseDetails purchase) async { + Future completePurchase(PurchaseDetails purchase) async { assert( purchase is GooglePlayPurchaseDetails, 'On Android, the `purchase` should always be of type `GooglePlayPurchaseDetails`.', ); - final GooglePlayPurchaseDetails googlePurchase = - purchase as GooglePlayPurchaseDetails; + final GooglePlayPurchaseDetails googlePurchase = purchase as GooglePlayPurchaseDetails; if (googlePurchase.billingClientPurchase.isAcknowledged) { return const BillingResultWrapper(responseCode: BillingResponse.ok); } if (googlePurchase.verificationData == null) { - throw ArgumentError( - 'completePurchase unsuccessful. The `purchase.verificationData` is not valid'); + throw ArgumentError('completePurchase unsuccessful. The `purchase.verificationData` is not valid'); } - return await billingClient - .acknowledgePurchase(purchase.verificationData.serverVerificationData); + return await _wrap(() => billingClient.acknowledgePurchase(purchase.verificationData.serverVerificationData)); } @override @@ -187,21 +169,16 @@ class InAppPurchaseAndroidPlatform extends InAppPurchasePlatform { ]); final Set errorCodeSet = responses - .where((PurchasesResultWrapper response) => - response.responseCode != BillingResponse.ok) - .map((PurchasesResultWrapper response) => - response.responseCode.toString()) + .where((PurchasesResultWrapper response) => response.responseCode != BillingResponse.ok) + .map((PurchasesResultWrapper response) => response.responseCode.toString()) .toSet(); - final String errorMessage = - errorCodeSet.isNotEmpty ? errorCodeSet.join(', ') : ''; + final String errorMessage = errorCodeSet.isNotEmpty ? errorCodeSet.join(', ') : ''; - final List pastPurchases = - responses.expand((PurchasesResultWrapper response) { + final List pastPurchases = responses.expand((PurchasesResultWrapper response) { return response.purchasesList; }).map((PurchaseWrapper purchaseWrapper) { - final GooglePlayPurchaseDetails purchaseDetails = - GooglePlayPurchaseDetails.fromPurchase(purchaseWrapper); + final GooglePlayPurchaseDetails purchaseDetails = GooglePlayPurchaseDetails.fromPurchase(purchaseWrapper); purchaseDetails.status = PurchaseStatus.restored; @@ -219,19 +196,27 @@ class InAppPurchaseAndroidPlatform extends InAppPurchasePlatform { _purchaseUpdatedController.add(pastPurchases); } - Future _connect() => - billingClient.startConnection(onBillingServiceDisconnected: () {}); + void _connect() { + _isInitializing = true; + _readyFuture = Future.sync(() async { + await billingClient.endConnection(); + await billingClient.startConnection(onBillingServiceDisconnected: () { + if (!_isInitializing) { + _connect(); + } + }); + _isInitializing = false; + }); + } - Future _maybeAutoConsumePurchase( - PurchaseDetails purchaseDetails) async { + Future _maybeAutoConsumePurchase(PurchaseDetails purchaseDetails) async { if (!(purchaseDetails.status == PurchaseStatus.purchased && _productIdsToConsume.contains(purchaseDetails.productID))) { return purchaseDetails; } final BillingResultWrapper billingResult = - await (InAppPurchasePlatformAddition.instance! - as InAppPurchaseAndroidPlatformAddition) + await (InAppPurchasePlatformAddition.instance! as InAppPurchaseAndroidPlatformAddition) .consumePurchase(purchaseDetails); final BillingResponse consumedResponse = billingResult.responseCode; if (consumedResponse != BillingResponse.ok) { @@ -248,8 +233,7 @@ class InAppPurchaseAndroidPlatform extends InAppPurchasePlatform { return purchaseDetails; } - Future> _getPurchaseDetailsFromResult( - PurchasesResultWrapper resultWrapper) async { + Future> _getPurchaseDetailsFromResult(PurchasesResultWrapper resultWrapper) async { IAPError? error; if (resultWrapper.responseCode != BillingResponse.ok) { error = IAPError( @@ -259,10 +243,9 @@ class InAppPurchaseAndroidPlatform extends InAppPurchasePlatform { details: resultWrapper.billingResult.debugMessage, ); } - final List> purchases = - resultWrapper.purchasesList.map((PurchaseWrapper purchase) { - final GooglePlayPurchaseDetails googlePlayPurchaseDetails = - GooglePlayPurchaseDetails.fromPurchase(purchase)..error = error; + final List> purchases = resultWrapper.purchasesList.map((PurchaseWrapper purchase) { + final GooglePlayPurchaseDetails googlePlayPurchaseDetails = GooglePlayPurchaseDetails.fromPurchase(purchase) + ..error = error; if (resultWrapper.responseCode == BillingResponse.userCanceled) { googlePlayPurchaseDetails.status = PurchaseStatus.canceled; } @@ -283,12 +266,21 @@ class InAppPurchaseAndroidPlatform extends InAppPurchasePlatform { productID: '', status: status, transactionDate: null, - verificationData: PurchaseVerificationData( - localVerificationData: '', - serverVerificationData: '', - source: kIAPSource)) + verificationData: + PurchaseVerificationData(localVerificationData: '', serverVerificationData: '', source: kIAPSource)) ..error = error ]; } } + + Future _wrap(Future Function() block) async { + final BillingResultWrapper result = await block(); + if (result.responseCode == BillingResponse.serviceDisconnected) { + await Future.delayed(const Duration(seconds: 3)); + await _readyFuture; + return await _wrap(block); + } else { + return result; + } + } } From c12ca18b8bc043e3364c44767c2299df1ec711a1 Mon Sep 17 00:00:00 2001 From: Jakub Walusiak Date: Wed, 24 Aug 2022 18:03:16 +0200 Subject: [PATCH 002/130] [in_app_purchases_android_platform] Extract BillingClientManager --- .../lib/billing_client_wrappers.dart | 1 + .../billing_client_manager.dart | 147 +++++++++++++ .../purchase_wrapper.dart | 8 +- .../sku_details_wrapper.dart | 16 +- .../src/in_app_purchase_android_platform.dart | 195 ++++++++++-------- ...pp_purchase_android_platform_addition.dart | 33 ++- .../billing_client_manager_test.dart | 101 +++++++++ ...rchase_android_platform_addition_test.dart | 2 +- ...in_app_purchase_android_platform_test.dart | 55 ++++- .../test/stub_in_app_purchase_platform.dart | 6 +- 10 files changed, 449 insertions(+), 115 deletions(-) create mode 100644 packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_manager.dart create mode 100644 packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/billing_client_manager_test.dart diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/billing_client_wrappers.dart b/packages/in_app_purchase/in_app_purchase_android/lib/billing_client_wrappers.dart index 1dac19f825b8..b49be8fe0fe1 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/billing_client_wrappers.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/billing_client_wrappers.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +export 'src/billing_client_wrappers/billing_client_manager.dart'; export 'src/billing_client_wrappers/billing_client_wrapper.dart'; export 'src/billing_client_wrappers/purchase_wrapper.dart'; export 'src/billing_client_wrappers/sku_details_wrapper.dart'; diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_manager.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_manager.dart new file mode 100644 index 000000000000..645be67eb0ef --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_manager.dart @@ -0,0 +1,147 @@ +// Copyright 2013 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. + +import 'dart:async'; + +import 'package:flutter/widgets.dart'; + +import 'billing_client_wrapper.dart'; +import 'purchase_wrapper.dart'; +import 'sku_details_wrapper.dart'; + +/// Utility class that manages a [BillingClient] connection. +/// +/// Connection is initialized on creation of [BillingClientManager]. +/// If [BillingClient] sends `onBillingServiceDisconnected` event or any +/// operation returns [BillingResponse.serviceDisconnected], connection is +/// re-initialized. +/// +/// [BillingClient] instance is not exposed directly. It can be accessed via +/// [run] and [runRaw] methods that handle the connection management. +/// +/// Consider calling [dispose] after the [BillingClient] is no longer needed. +class BillingClientManager { + /// Creates the [BillingClientManager]. + /// + /// Immediately initializes connection to the underlying [BillingClient]. + BillingClientManager() { + _connect(); + } + + /// Stream of `onPurchasesUpdated` events from the [BillingClient]. + /// + /// This is a broadcast stream, so it can be listened to multiple times. + /// A "done" event will be sent after [dispose] is called. + late final Stream purchasesUpdatedStream = + _purchasesUpdatedController.stream; + + /// [BillingClient] instance managed by this [BillingClientManager]. + /// + /// This field should not be accessed outside of test code. + /// In order to access the [BillingClient], consider using [run] and [runRaw] + /// methods. + @visibleForTesting + late final BillingClient client = BillingClient(_onPurchasesUpdated); + + final StreamController _purchasesUpdatedController = + StreamController.broadcast(); + + bool _isConnecting = false; + bool _isDisposed = false; + + // Initialized immediately in the constructor, so it's always safe to access. + late Future _readyFuture; + + /// Executes the given [block] with access to the underlying [BillingClient]. + /// + /// If necessary, waits for the underlying [BillingClient] to connect. + /// If given [block] returns [BillingResponse.serviceDisconnected], it will + /// be transparently retried after the connection is restored. Because + /// of this, [block] may be called multiple times. + /// A response with [BillingResponse.serviceDisconnected] may be returned + /// in case of [dispose] being called during the operation. + /// + /// See [runRaw] for operations that do not return a subclass + /// of [HasBillingResponse]. + Future run( + Future Function(BillingClient client) block, + ) async { + assert(_debugAssertNotDisposed()); + await _readyFuture; + final R result = await block(client); + if (result.responseCode == BillingResponse.serviceDisconnected && + !_isDisposed) { + await _connect(); + return await run(block); + } else { + return result; + } + } + + /// Executes the given [block] with access to the underlying [BillingClient]. + /// + /// If necessary, waits for the underlying [BillingClient] to connect. + /// Designed only for operations that do not return a subclass + /// of [HasBillingResponse] (e.g. [BillingClient.isReady], + /// [BillingClient.isFeatureSupported]). + /// + /// See [runRaw] for operations that return a subclass + /// of [HasBillingResponse]. + Future runRaw(Future Function(BillingClient client) block) async { + assert(_debugAssertNotDisposed()); + await _readyFuture; + return await block(client); + } + + /// Ends connection to the [BillingClient]. + /// + /// After calling [dispose] : + /// - Further connection attempts will not be made; + /// - [purchasesUpdatedStream] will be closed; + /// - Calls to [run] and [runRaw] will throw. + void dispose() { + assert(_debugAssertNotDisposed()); + _isDisposed = true; + client.endConnection(); + _purchasesUpdatedController.close(); + } + + // If disposed, does nothing. + // If currently connecting, waits for it to complete. + // Otherwise, starts a new connection. + Future _connect() { + if (_isDisposed) { + return Future.value(); + } + if (_isConnecting) { + return _readyFuture; + } + _isConnecting = true; + _readyFuture = Future.sync(() async { + await client.startConnection(onBillingServiceDisconnected: _connect); + _isConnecting = false; + }); + return _readyFuture; + } + + void _onPurchasesUpdated(PurchasesResultWrapper event) { + if (_isDisposed) { + return; + } + _purchasesUpdatedController.add(event); + } + + bool _debugAssertNotDisposed() { + assert(() { + if (_isDisposed) { + throw FlutterError( + 'A BillingClientManager was used after being disposed.\n' + 'Once you have called dispose() on a BillingClientManager, it can no longer be used.', + ); + } + return true; + }()); + return true; + } +} diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/purchase_wrapper.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/purchase_wrapper.dart index 4e6b953096e2..a231e4f1bb54 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/purchase_wrapper.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/purchase_wrapper.dart @@ -265,7 +265,7 @@ class PurchaseHistoryRecordWrapper { @JsonSerializable() @BillingResponseConverter() @immutable -class PurchasesResultWrapper { +class PurchasesResultWrapper implements HasBillingResponse { /// Creates a [PurchasesResultWrapper] with the given purchase result details. const PurchasesResultWrapper( {required this.responseCode, @@ -300,6 +300,7 @@ class PurchasesResultWrapper { /// /// This can represent either the status of the "query purchase history" half /// of the operation and the "user made purchases" transaction itself. + @override final BillingResponse responseCode; /// The list of successful purchases made in this transaction. @@ -316,7 +317,7 @@ class PurchasesResultWrapper { @JsonSerializable() @BillingResponseConverter() @immutable -class PurchasesHistoryResult { +class PurchasesHistoryResult implements HasBillingResponse { /// Creates a [PurchasesHistoryResult] with the provided history. const PurchasesHistoryResult( {required this.billingResult, required this.purchaseHistoryRecordList}); @@ -325,6 +326,9 @@ class PurchasesHistoryResult { factory PurchasesHistoryResult.fromJson(Map map) => _$PurchasesHistoryResultFromJson(map); + @override + BillingResponse get responseCode => billingResult.responseCode; + @override bool operator ==(Object other) { if (identical(other, this)) { diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/sku_details_wrapper.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/sku_details_wrapper.dart index 1c5c2d1fcee9..129a63df67d6 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/sku_details_wrapper.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/sku_details_wrapper.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'package:flutter/foundation.dart'; +import 'package:in_app_purchase_android/billing_client_wrappers.dart'; import 'package:json_annotation/json_annotation.dart'; import 'billing_client_wrapper.dart'; @@ -19,6 +20,13 @@ part 'sku_details_wrapper.g.dart'; const String kInvalidBillingResultErrorMessage = 'Invalid billing result map from method channel.'; +/// Abstraction of result of [BillingClient] operation that includes +/// a [BillingResponse]. +abstract class HasBillingResponse { + /// The status of the operation. + abstract final BillingResponse responseCode; +} + /// Dart wrapper around [`com.android.billingclient.api.SkuDetails`](https://developer.android.com/reference/com/android/billingclient/api/SkuDetails). /// /// Contains the details of an available product in Google Play Billing. @@ -182,7 +190,7 @@ class SkuDetailsWrapper { /// Returned by [BillingClient.querySkuDetails]. @JsonSerializable() @immutable -class SkuDetailsResponseWrapper { +class SkuDetailsResponseWrapper implements HasBillingResponse { /// Creates a [SkuDetailsResponseWrapper] with the given purchase details. @visibleForTesting const SkuDetailsResponseWrapper( @@ -202,6 +210,9 @@ class SkuDetailsResponseWrapper { @JsonKey(defaultValue: []) final List skuDetailsList; + @override + BillingResponse get responseCode => billingResult.responseCode; + @override bool operator ==(Object other) { if (other.runtimeType != runtimeType) { @@ -221,7 +232,7 @@ class SkuDetailsResponseWrapper { @JsonSerializable() @BillingResponseConverter() @immutable -class BillingResultWrapper { +class BillingResultWrapper implements HasBillingResponse { /// Constructs the object with [responseCode] and [debugMessage]. const BillingResultWrapper({required this.responseCode, this.debugMessage}); @@ -239,6 +250,7 @@ class BillingResultWrapper { } /// Response code returned in the Play Billing API calls. + @override final BillingResponse responseCode; /// Debug message returned in the Play Billing API calls. diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform.dart index 31ab72e80b67..94639c70967d 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform.dart @@ -29,15 +29,13 @@ const String kIAPSource = 'google_play'; /// generic plugin API. class InAppPurchaseAndroidPlatform extends InAppPurchasePlatform { InAppPurchaseAndroidPlatform._() { - billingClient = BillingClient((PurchasesResultWrapper resultWrapper) async { - _purchaseUpdatedController.add(await _getPurchaseDetailsFromResult(resultWrapper)); - }); - // Register [InAppPurchaseAndroidPlatformAddition]. - InAppPurchasePlatformAddition.instance = InAppPurchaseAndroidPlatformAddition(billingClient); + InAppPurchasePlatformAddition.instance = + InAppPurchaseAndroidPlatformAddition(billingClientManager); - _connect(); - _purchaseUpdatedController = StreamController>.broadcast(); + billingClientManager.purchasesUpdatedStream + .asyncMap(_getPurchaseDetailsFromResult) + .listen(_purchaseUpdatedController.add); } /// Registers this class as the default instance of [InAppPurchasePlatform]. @@ -47,57 +45,72 @@ class InAppPurchaseAndroidPlatform extends InAppPurchasePlatform { InAppPurchasePlatform.instance = InAppPurchaseAndroidPlatform._(); } - static late StreamController> _purchaseUpdatedController; + final StreamController> _purchaseUpdatedController = + StreamController>.broadcast(); @override - Stream> get purchaseStream => _purchaseUpdatedController.stream; + late final Stream> purchaseStream = + _purchaseUpdatedController.stream; /// The [BillingClient] that's abstracted by [GooglePlayConnection]. /// /// This field should not be used out of test code. @visibleForTesting - late final BillingClient billingClient; + final BillingClientManager billingClientManager = BillingClientManager(); - bool _isInitializing = true; - late Future _readyFuture; static final Set _productIdsToConsume = {}; @override Future isAvailable() async { - await _readyFuture; - return billingClient.isReady(); + return await billingClientManager + .runRaw((BillingClient client) => client.isReady()); } @override - Future queryProductDetails(Set identifiers) async { + Future queryProductDetails( + Set identifiers, + ) async { List responses; PlatformException? exception; + + Future querySkuDetails(SkuType type) { + return billingClientManager.run( + (BillingClient client) => client.querySkuDetails( + skuType: type, + skusList: identifiers.toList(), + ), + ); + } + try { responses = await Future.wait(>[ - billingClient.querySkuDetails(skuType: SkuType.inapp, skusList: identifiers.toList()), - billingClient.querySkuDetails(skuType: SkuType.subs, skusList: identifiers.toList()) + querySkuDetails(SkuType.inapp), + querySkuDetails(SkuType.subs), ]); } on PlatformException catch (e) { exception = e; - responses = [ - // ignore: invalid_use_of_visible_for_testing_member - SkuDetailsResponseWrapper( - billingResult: BillingResultWrapper(responseCode: BillingResponse.error, debugMessage: e.code), - skuDetailsList: const []), - // ignore: invalid_use_of_visible_for_testing_member - SkuDetailsResponseWrapper( - billingResult: BillingResultWrapper(responseCode: BillingResponse.error, debugMessage: e.code), - skuDetailsList: const []) - ]; + // ignore: invalid_use_of_visible_for_testing_member + final SkuDetailsResponseWrapper response = SkuDetailsResponseWrapper( + billingResult: BillingResultWrapper( + responseCode: BillingResponse.error, + debugMessage: e.code, + ), + skuDetailsList: const [], + ); + responses = [response, response]; } - final List productDetailsList = responses.expand((SkuDetailsResponseWrapper response) { + final List productDetailsList = + responses.expand((SkuDetailsResponseWrapper response) { return response.skuDetailsList; }).map((SkuDetailsWrapper skuDetailWrapper) { return GooglePlayProductDetails.fromSkuDetails(skuDetailWrapper); }).toList(); - final Set successIDS = productDetailsList.map((ProductDetails productDetails) => productDetails.id).toSet(); - final List notFoundIDS = identifiers.difference(successIDS).toList(); + final Set successIDS = productDetailsList + .map((ProductDetails productDetails) => productDetails.id) + .toSet(); + final List notFoundIDS = + identifiers.difference(successIDS).toList(); return ProductDetailsResponse( productDetails: productDetailsList, notFoundIDs: notFoundIDS, @@ -118,19 +131,23 @@ class InAppPurchaseAndroidPlatform extends InAppPurchasePlatform { changeSubscriptionParam = purchaseParam.changeSubscriptionParam; } - final BillingResultWrapper billingResultWrapper = await _wrap( - () => billingClient.launchBillingFlow( - sku: purchaseParam.productDetails.id, - accountId: purchaseParam.applicationUserName, - oldSku: changeSubscriptionParam?.oldPurchaseDetails.productID, - purchaseToken: changeSubscriptionParam?.oldPurchaseDetails.verificationData.serverVerificationData, - prorationMode: changeSubscriptionParam?.prorationMode), + final BillingResultWrapper billingResultWrapper = + await billingClientManager.run( + (BillingClient client) => client.launchBillingFlow( + sku: purchaseParam.productDetails.id, + accountId: purchaseParam.applicationUserName, + oldSku: changeSubscriptionParam?.oldPurchaseDetails.productID, + purchaseToken: changeSubscriptionParam + ?.oldPurchaseDetails.verificationData.serverVerificationData, + prorationMode: changeSubscriptionParam?.prorationMode, + ), ); return billingResultWrapper.responseCode == BillingResponse.ok; } @override - Future buyConsumable({required PurchaseParam purchaseParam, bool autoConsume = true}) { + Future buyConsumable( + {required PurchaseParam purchaseParam, bool autoConsume = true}) { if (autoConsume) { _productIdsToConsume.add(purchaseParam.productDetails.id); } @@ -138,47 +155,59 @@ class InAppPurchaseAndroidPlatform extends InAppPurchasePlatform { } @override - Future completePurchase(PurchaseDetails purchase) async { + Future completePurchase( + PurchaseDetails purchase, + ) async { assert( purchase is GooglePlayPurchaseDetails, 'On Android, the `purchase` should always be of type `GooglePlayPurchaseDetails`.', ); - final GooglePlayPurchaseDetails googlePurchase = purchase as GooglePlayPurchaseDetails; + final GooglePlayPurchaseDetails googlePurchase = + purchase as GooglePlayPurchaseDetails; if (googlePurchase.billingClientPurchase.isAcknowledged) { return const BillingResultWrapper(responseCode: BillingResponse.ok); } if (googlePurchase.verificationData == null) { - throw ArgumentError('completePurchase unsuccessful. The `purchase.verificationData` is not valid'); + throw ArgumentError( + 'completePurchase unsuccessful. The `purchase.verificationData` is not valid'); } - return await _wrap(() => billingClient.acknowledgePurchase(purchase.verificationData.serverVerificationData)); + return await billingClientManager.run( + (BillingClient client) => client.acknowledgePurchase( + purchase.verificationData.serverVerificationData), + ); } @override - Future restorePurchases({ - String? applicationUserName, - }) async { + Future restorePurchases({String? applicationUserName}) async { List responses; responses = await Future.wait(>[ - billingClient.queryPurchases(SkuType.inapp), - billingClient.queryPurchases(SkuType.subs) + billingClientManager + .run((BillingClient client) => client.queryPurchases(SkuType.inapp)), + billingClientManager + .run((BillingClient client) => client.queryPurchases(SkuType.subs)), ]); final Set errorCodeSet = responses - .where((PurchasesResultWrapper response) => response.responseCode != BillingResponse.ok) - .map((PurchasesResultWrapper response) => response.responseCode.toString()) + .where((PurchasesResultWrapper response) => + response.responseCode != BillingResponse.ok) + .map((PurchasesResultWrapper response) => + response.responseCode.toString()) .toSet(); - final String errorMessage = errorCodeSet.isNotEmpty ? errorCodeSet.join(', ') : ''; + final String errorMessage = + errorCodeSet.isNotEmpty ? errorCodeSet.join(', ') : ''; - final List pastPurchases = responses.expand((PurchasesResultWrapper response) { + final List pastPurchases = + responses.expand((PurchasesResultWrapper response) { return response.purchasesList; }).map((PurchaseWrapper purchaseWrapper) { - final GooglePlayPurchaseDetails purchaseDetails = GooglePlayPurchaseDetails.fromPurchase(purchaseWrapper); + final GooglePlayPurchaseDetails purchaseDetails = + GooglePlayPurchaseDetails.fromPurchase(purchaseWrapper); purchaseDetails.status = PurchaseStatus.restored; @@ -196,28 +225,19 @@ class InAppPurchaseAndroidPlatform extends InAppPurchasePlatform { _purchaseUpdatedController.add(pastPurchases); } - void _connect() { - _isInitializing = true; - _readyFuture = Future.sync(() async { - await billingClient.endConnection(); - await billingClient.startConnection(onBillingServiceDisconnected: () { - if (!_isInitializing) { - _connect(); - } - }); - _isInitializing = false; - }); - } - - Future _maybeAutoConsumePurchase(PurchaseDetails purchaseDetails) async { + Future _maybeAutoConsumePurchase( + PurchaseDetails purchaseDetails, + ) async { if (!(purchaseDetails.status == PurchaseStatus.purchased && _productIdsToConsume.contains(purchaseDetails.productID))) { return purchaseDetails; } + final InAppPurchaseAndroidPlatformAddition addition = + InAppPurchasePlatformAddition.instance! + as InAppPurchaseAndroidPlatformAddition; final BillingResultWrapper billingResult = - await (InAppPurchasePlatformAddition.instance! as InAppPurchaseAndroidPlatformAddition) - .consumePurchase(purchaseDetails); + await addition.consumePurchase(purchaseDetails); final BillingResponse consumedResponse = billingResult.responseCode; if (consumedResponse != BillingResponse.ok) { purchaseDetails.status = PurchaseStatus.error; @@ -233,7 +253,9 @@ class InAppPurchaseAndroidPlatform extends InAppPurchasePlatform { return purchaseDetails; } - Future> _getPurchaseDetailsFromResult(PurchasesResultWrapper resultWrapper) async { + Future> _getPurchaseDetailsFromResult( + PurchasesResultWrapper resultWrapper, + ) async { IAPError? error; if (resultWrapper.responseCode != BillingResponse.ok) { error = IAPError( @@ -243,9 +265,10 @@ class InAppPurchaseAndroidPlatform extends InAppPurchasePlatform { details: resultWrapper.billingResult.debugMessage, ); } - final List> purchases = resultWrapper.purchasesList.map((PurchaseWrapper purchase) { - final GooglePlayPurchaseDetails googlePlayPurchaseDetails = GooglePlayPurchaseDetails.fromPurchase(purchase) - ..error = error; + final List> purchases = + resultWrapper.purchasesList.map((PurchaseWrapper purchase) { + final GooglePlayPurchaseDetails googlePlayPurchaseDetails = + GooglePlayPurchaseDetails.fromPurchase(purchase)..error = error; if (resultWrapper.responseCode == BillingResponse.userCanceled) { googlePlayPurchaseDetails.status = PurchaseStatus.canceled; } @@ -262,25 +285,17 @@ class InAppPurchaseAndroidPlatform extends InAppPurchasePlatform { } return [ PurchaseDetails( - purchaseID: '', - productID: '', - status: status, - transactionDate: null, - verificationData: - PurchaseVerificationData(localVerificationData: '', serverVerificationData: '', source: kIAPSource)) - ..error = error + purchaseID: '', + productID: '', + status: status, + transactionDate: null, + verificationData: PurchaseVerificationData( + localVerificationData: '', + serverVerificationData: '', + source: kIAPSource, + ), + )..error = error ]; } } - - Future _wrap(Future Function() block) async { - final BillingResultWrapper result = await block(); - if (result.responseCode == BillingResponse.serviceDisconnected) { - await Future.delayed(const Duration(seconds: 3)); - await _readyFuture; - return await _wrap(block); - } else { - return result; - } - } } diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform_addition.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform_addition.dart index db53ff4077d2..bf865d72d328 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform_addition.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform_addition.dart @@ -12,8 +12,8 @@ import '../billing_client_wrappers.dart'; class InAppPurchaseAndroidPlatformAddition extends InAppPurchasePlatformAddition { /// Creates a [InAppPurchaseAndroidPlatformAddition] which uses the supplied - /// `BillingClient` to provide Android specific features. - InAppPurchaseAndroidPlatformAddition(this._billingClient); + /// `BillingClientManager` to provide Android specific features. + InAppPurchaseAndroidPlatformAddition(this._billingClientManager); /// Whether pending purchase is enabled. /// @@ -42,7 +42,7 @@ class InAppPurchaseAndroidPlatformAddition // No-op, until it is time to completely remove this method from the API. } - final BillingClient _billingClient; + final BillingClientManager _billingClientManager; /// Mark that the user has consumed a product. /// @@ -54,8 +54,10 @@ class InAppPurchaseAndroidPlatformAddition throw ArgumentError( 'consumePurchase unsuccessful. The `purchase.verificationData` is not valid'); } - return _billingClient - .consumeAsync(purchase.verificationData.serverVerificationData); + return _billingClientManager.run( + (BillingClient client) => + client.consumeAsync(purchase.verificationData.serverVerificationData), + ); } /// Query all previous purchases. @@ -78,8 +80,12 @@ class InAppPurchaseAndroidPlatformAddition PlatformException? exception; try { responses = await Future.wait(>[ - _billingClient.queryPurchases(SkuType.inapp), - _billingClient.queryPurchases(SkuType.subs) + _billingClientManager.run( + (BillingClient client) => client.queryPurchases(SkuType.inapp), + ), + _billingClientManager.run( + (BillingClient client) => client.queryPurchases(SkuType.subs), + ), ]); } on PlatformException catch (e) { exception = e; @@ -141,7 +147,8 @@ class InAppPurchaseAndroidPlatformAddition /// Checks if the specified feature or capability is supported by the Play Store. /// Call this to check if a [BillingClientFeature] is supported by the device. Future isFeatureSupported(BillingClientFeature feature) async { - return _billingClient.isFeatureSupported(feature); + return _billingClientManager + .runRaw((BillingClient client) => client.isFeatureSupported(feature)); } /// Initiates a flow to confirm the change of price for an item subscribed by the user. @@ -151,8 +158,12 @@ class InAppPurchaseAndroidPlatformAddition /// /// The skuDetails needs to have already been fetched in a /// [InAppPurchaseAndroidPlatform.queryProductDetails] call. - Future launchPriceChangeConfirmationFlow( - {required String sku}) { - return _billingClient.launchPriceChangeConfirmationFlow(sku: sku); + Future launchPriceChangeConfirmationFlow({ + required String sku, + }) { + return _billingClientManager.run( + (BillingClient client) => + client.launchPriceChangeConfirmationFlow(sku: sku), + ); } } diff --git a/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/billing_client_manager_test.dart b/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/billing_client_manager_test.dart new file mode 100644 index 000000000000..b329dd8d51f4 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/billing_client_manager_test.dart @@ -0,0 +1,101 @@ +// Copyright 2013 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. + +import 'dart:async'; + +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:in_app_purchase_android/billing_client_wrappers.dart'; +import 'package:in_app_purchase_android/src/channel.dart'; + +import '../stub_in_app_purchase_platform.dart'; +import 'purchase_wrapper_test.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + final StubInAppPurchasePlatform stubPlatform = StubInAppPurchasePlatform(); + late BillingClientManager manager; + late Completer connectedCompleter; + + const String startConnectionCall = + 'BillingClient#startConnection(BillingClientStateListener)'; + const String endConnectionCall = 'BillingClient#endConnection()'; + const String onBillingServiceDisconnectedCallback = + 'BillingClientStateListener#onBillingServiceDisconnected()'; + + setUpAll(() => + channel.setMockMethodCallHandler(stubPlatform.fakeMethodCallHandler)); + + setUp(() { + WidgetsFlutterBinding.ensureInitialized(); + connectedCompleter = Completer.sync(); + stubPlatform.addResponse( + name: startConnectionCall, + value: buildBillingResultMap( + const BillingResultWrapper(responseCode: BillingResponse.ok), + ), + additionalStepBeforeReturn: (dynamic _) => connectedCompleter.future, + ); + stubPlatform.addResponse(name: endConnectionCall); + manager = BillingClientManager(); + }); + + tearDown(() => stubPlatform.reset()); + + group('BillingClientWrapper', () { + test('connects on initialization', () { + expect(stubPlatform.countPreviousCalls(startConnectionCall), equals(1)); + }); + test('waits for connection before executing the operations', () { + bool runCalled = false; + bool runRawCalled = false; + manager.run((BillingClient _) async { + runCalled = true; + return const BillingResultWrapper(responseCode: BillingResponse.ok); + }); + manager.runRaw((BillingClient _) async => runRawCalled = true); + expect(runCalled, equals(false)); + expect(runRawCalled, equals(false)); + connectedCompleter.complete(); + expect(runCalled, equals(true)); + expect(runRawCalled, equals(true)); + }); + test('re-connects when client sends onBillingServiceDisconnected', () { + connectedCompleter.complete(); + manager.client.callHandler( + const MethodCall(onBillingServiceDisconnectedCallback, + {'handle': 0}), + ); + expect(stubPlatform.countPreviousCalls(startConnectionCall), equals(2)); + }); + test( + 're-connects when operation returns BillingResponse.serviceDisconnected', + () async { + connectedCompleter.complete(); + int timesCalled = 0; + final BillingResultWrapper result = await manager.run( + (BillingClient _) async { + timesCalled++; + return BillingResultWrapper( + responseCode: timesCalled == 1 + ? BillingResponse.serviceDisconnected + : BillingResponse.ok, + ); + }, + ); + expect(stubPlatform.countPreviousCalls(startConnectionCall), equals(2)); + expect(timesCalled, equals(2)); + expect(result.responseCode, equals(BillingResponse.ok)); + }, + ); + test('does not re-connect when disposed', () { + connectedCompleter.complete(); + manager.dispose(); + expect(stubPlatform.countPreviousCalls(startConnectionCall), equals(1)); + expect(stubPlatform.countPreviousCalls(endConnectionCall), equals(1)); + }); + }); +} diff --git a/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_addition_test.dart b/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_addition_test.dart index 9737282e27b7..9f2feb3c14a8 100644 --- a/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_addition_test.dart +++ b/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_addition_test.dart @@ -37,7 +37,7 @@ void main() { value: buildBillingResultMap(expectedBillingResult)); stubPlatform.addResponse(name: endConnectionCall); iapAndroidPlatformAddition = - InAppPurchaseAndroidPlatformAddition(BillingClient((_) {})); + InAppPurchaseAndroidPlatformAddition(BillingClientManager()); }); group('consume purchases', () { diff --git a/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_test.dart b/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_test.dart index b6055cc9a8bb..397e7e134ce2 100644 --- a/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_test.dart +++ b/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_test.dart @@ -24,6 +24,10 @@ void main() { const String startConnectionCall = 'BillingClient#startConnection(BillingClientStateListener)'; const String endConnectionCall = 'BillingClient#endConnection()'; + const String acknowledgePurchaseCall = + 'BillingClient#(AcknowledgePurchaseParams params, (AcknowledgePurchaseParams, AcknowledgePurchaseResponseListener)'; + const String onBillingServiceDisconnectedCallback = + 'BillingClientStateListener#onBillingServiceDisconnected()'; setUpAll(() { channel.setMockMethodCallHandler(stubPlatform.fakeMethodCallHandler); @@ -55,6 +59,43 @@ void main() { //await iapAndroidPlatform.isAvailable(); expect(stubPlatform.countPreviousCalls(startConnectionCall), equals(1)); }); + test('re-connects when client sends onBillingServiceDisconnected', () { + iapAndroidPlatform.billingClientManager.client.callHandler( + const MethodCall(onBillingServiceDisconnectedCallback, + {'handle': 0}), + ); + expect(stubPlatform.countPreviousCalls(startConnectionCall), equals(2)); + }); + test( + 're-connects when operation returns BillingResponse.clientDisconnected', + () async { + final Map okValue = buildBillingResultMap( + const BillingResultWrapper(responseCode: BillingResponse.ok)); + stubPlatform.addResponse( + name: acknowledgePurchaseCall, + value: buildBillingResultMap( + const BillingResultWrapper( + responseCode: BillingResponse.serviceDisconnected, + ), + ), + ); + stubPlatform.addResponse( + name: startConnectionCall, + value: okValue, + additionalStepBeforeReturn: (dynamic _) => stubPlatform.addResponse( + name: acknowledgePurchaseCall, value: okValue), + ); + final PurchaseDetails purchase = + GooglePlayPurchaseDetails.fromPurchase(dummyUnacknowledgedPurchase); + final BillingResultWrapper result = + await iapAndroidPlatform.completePurchase(purchase); + expect( + stubPlatform.countPreviousCalls(acknowledgePurchaseCall), + equals(2), + ); + expect(stubPlatform.countPreviousCalls(startConnectionCall), equals(2)); + expect(result.responseCode, equals(BillingResponse.ok)); + }); }); group('isAvailable', () { @@ -312,7 +353,7 @@ void main() { } ] }); - iapAndroidPlatform.billingClient.callHandler(call); + iapAndroidPlatform.billingClientManager.client.callHandler(call); }); final Completer completer = Completer(); PurchaseDetails purchaseDetails; @@ -356,7 +397,7 @@ void main() { 'responseCode': const BillingResponseConverter().toJson(sentCode), 'purchasesList': const [] }); - iapAndroidPlatform.billingClient.callHandler(call); + iapAndroidPlatform.billingClientManager.client.callHandler(call); }); final Completer completer = Completer(); PurchaseDetails purchaseDetails; @@ -414,7 +455,7 @@ void main() { } ] }); - iapAndroidPlatform.billingClient.callHandler(call); + iapAndroidPlatform.billingClientManager.client.callHandler(call); }); final Completer consumeCompleter = Completer(); // adding call back for consume purchase @@ -528,7 +569,7 @@ void main() { } ] }); - iapAndroidPlatform.billingClient.callHandler(call); + iapAndroidPlatform.billingClientManager.client.callHandler(call); }); final Completer consumeCompleter = Completer(); // adding call back for consume purchase @@ -605,7 +646,7 @@ void main() { } ] }); - iapAndroidPlatform.billingClient.callHandler(call); + iapAndroidPlatform.billingClientManager.client.callHandler(call); }); final Completer consumeCompleter = Completer(); // adding call back for consume purchase @@ -670,7 +711,7 @@ void main() { } ] }); - iapAndroidPlatform.billingClient.callHandler(call); + iapAndroidPlatform.billingClientManager.client.callHandler(call); }); final Completer consumeCompleter = Completer(); // adding call back for consume purchase @@ -727,7 +768,7 @@ void main() { 'responseCode': const BillingResponseConverter().toJson(sentCode), 'purchasesList': const [] }); - iapAndroidPlatform.billingClient.callHandler(call); + iapAndroidPlatform.billingClientManager.client.callHandler(call); }); final Completer completer = Completer(); diff --git a/packages/in_app_purchase/in_app_purchase_android/test/stub_in_app_purchase_platform.dart b/packages/in_app_purchase/in_app_purchase_android/test/stub_in_app_purchase_platform.dart index 75972e644faa..35e2807bc3b1 100644 --- a/packages/in_app_purchase/in_app_purchase_android/test/stub_in_app_purchase_platform.dart +++ b/packages/in_app_purchase/in_app_purchase_android/test/stub_in_app_purchase_platform.dart @@ -5,7 +5,9 @@ import 'dart:async'; import 'package:flutter/services.dart'; -typedef AdditionalSteps = void Function(dynamic args); +// `FutureOr` instead of `FutureOr` to avoid +// "don't assign to void" warnings. +typedef AdditionalSteps = FutureOr Function(dynamic args); class StubInAppPurchasePlatform { final Map _expectedCalls = {}; @@ -36,7 +38,7 @@ class StubInAppPurchasePlatform { _previousCalls.add(call); if (_expectedCalls.containsKey(call.method)) { if (_additionalSteps[call.method] != null) { - _additionalSteps[call.method]!(call.arguments); + await _additionalSteps[call.method]!(call.arguments); } return Future.sync(() => _expectedCalls[call.method]); } else { From 9f921dfdf305c8ac96ca2ad3c6f8b487fe9175a1 Mon Sep 17 00:00:00 2001 From: Jakub Walusiak Date: Wed, 31 Aug 2022 13:50:39 +0200 Subject: [PATCH 003/130] [in_app_purchases_android] Update changelog --- packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md b/packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md index 910c5cdc1fc0..0d6fd852b824 100644 --- a/packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md +++ b/packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md @@ -1,6 +1,8 @@ -## NEXT +## 0.2.4 * Updates minimum Flutter version to 2.10. +* Fixes the management of `BillingClient` connection. +* Introduces `BillingClientManager`. ## 0.2.3+3 From 732cda143a4aedac200a7a0bee6f82cb8643ac39 Mon Sep 17 00:00:00 2001 From: Jakub Walusiak Date: Wed, 31 Aug 2022 17:09:45 +0200 Subject: [PATCH 004/130] [in_app_purchases_android] Update pubspec.yaml --- packages/in_app_purchase/in_app_purchase_android/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/in_app_purchase/in_app_purchase_android/pubspec.yaml b/packages/in_app_purchase/in_app_purchase_android/pubspec.yaml index 320e4b818e83..aa34582000cc 100644 --- a/packages/in_app_purchase/in_app_purchase_android/pubspec.yaml +++ b/packages/in_app_purchase/in_app_purchase_android/pubspec.yaml @@ -2,7 +2,7 @@ name: in_app_purchase_android description: An implementation for the Android platform of the Flutter `in_app_purchase` plugin. This uses the Android BillingClient APIs. repository: https://github.com/flutter/plugins/tree/main/packages/in_app_purchase/in_app_purchase_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+in_app_purchase%22 -version: 0.2.3+3 +version: 0.2.4 environment: sdk: ">=2.14.0 <3.0.0" From 2fe4bb4ff478f067c20cefabe20cd9824382076a Mon Sep 17 00:00:00 2001 From: Jakub Walusiak Date: Tue, 4 Oct 2022 11:32:22 +0200 Subject: [PATCH 005/130] [in_app_purchases_android_platform] Enhance documentation, move HasBillingResponse --- .../billing_client_manager.dart | 13 +++++++++++-- .../billing_client_wrapper.dart | 6 ++++++ .../billing_client_wrappers/purchase_wrapper.dart | 1 + .../sku_details_wrapper.dart | 10 +--------- .../lib/src/in_app_purchase_android_platform.dart | 7 +++---- 5 files changed, 22 insertions(+), 15 deletions(-) diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_manager.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_manager.dart index 645be67eb0ef..4427032ef910 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_manager.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_manager.dart @@ -8,7 +8,13 @@ import 'package:flutter/widgets.dart'; import 'billing_client_wrapper.dart'; import 'purchase_wrapper.dart'; -import 'sku_details_wrapper.dart'; + +/// Abstraction of result of [BillingClient] operation that includes +/// a [BillingResponse]. +abstract class HasBillingResponse { + /// The status of the operation. + abstract final BillingResponse responseCode; +} /// Utility class that manages a [BillingClient] connection. /// @@ -38,7 +44,6 @@ class BillingClientManager { /// [BillingClient] instance managed by this [BillingClientManager]. /// - /// This field should not be accessed outside of test code. /// In order to access the [BillingClient], consider using [run] and [runRaw] /// methods. @visibleForTesting @@ -59,6 +64,7 @@ class BillingClientManager { /// If given [block] returns [BillingResponse.serviceDisconnected], it will /// be transparently retried after the connection is restored. Because /// of this, [block] may be called multiple times. + /// /// A response with [BillingResponse.serviceDisconnected] may be returned /// in case of [dispose] being called during the operation. /// @@ -96,6 +102,9 @@ class BillingClientManager { /// Ends connection to the [BillingClient]. /// + /// Consider calling [dispose] after you no longer need the [BillingClient] + /// API to free up the resources. + /// /// After calling [dispose] : /// - Further connection attempts will not be made; /// - [purchasesUpdatedStream] will be closed; diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_wrapper.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_wrapper.dart index b64eaab49a9d..042ff9026cf0 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_wrapper.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_wrapper.dart @@ -51,6 +51,12 @@ typedef PurchasesUpdatedListener = void Function( /// `com.android.billingclient.api.BillingClient` API as much as possible, with /// some minor changes to account for language differences. Callbacks have been /// converted to futures where appropriate. +/// +/// Connection to [BillingClient] may be lost at any time (see +/// `onBillingServiceDisconnected` param of [startConnection] and +/// [BillingResponse.serviceDisconnected]). +/// Consider using [BillingClientManager] that handles these disconnections +/// transparently. class BillingClient { /// Creates a billing client. BillingClient(PurchasesUpdatedListener onPurchasesUpdated) { diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/purchase_wrapper.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/purchase_wrapper.dart index a231e4f1bb54..633aa732165b 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/purchase_wrapper.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/purchase_wrapper.dart @@ -6,6 +6,7 @@ import 'package:flutter/foundation.dart'; import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart'; import 'package:json_annotation/json_annotation.dart'; +import 'billing_client_manager.dart'; import 'billing_client_wrapper.dart'; import 'sku_details_wrapper.dart'; diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/sku_details_wrapper.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/sku_details_wrapper.dart index 129a63df67d6..5ef13c2e2de2 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/sku_details_wrapper.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/sku_details_wrapper.dart @@ -3,10 +3,9 @@ // found in the LICENSE file. import 'package:flutter/foundation.dart'; -import 'package:in_app_purchase_android/billing_client_wrappers.dart'; import 'package:json_annotation/json_annotation.dart'; -import 'billing_client_wrapper.dart'; +import '../../billing_client_wrappers.dart'; // WARNING: Changes to `@JsonSerializable` classes need to be reflected in the // below generated file. Run `flutter packages pub run build_runner watch` to @@ -20,13 +19,6 @@ part 'sku_details_wrapper.g.dart'; const String kInvalidBillingResultErrorMessage = 'Invalid billing result map from method channel.'; -/// Abstraction of result of [BillingClient] operation that includes -/// a [BillingResponse]. -abstract class HasBillingResponse { - /// The status of the operation. - abstract final BillingResponse responseCode; -} - /// Dart wrapper around [`com.android.billingclient.api.SkuDetails`](https://developer.android.com/reference/com/android/billingclient/api/SkuDetails). /// /// Contains the details of an available product in Google Play Billing. diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform.dart index 91245574381f..6349842d5f66 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform.dart @@ -233,11 +233,10 @@ class InAppPurchaseAndroidPlatform extends InAppPurchasePlatform { return purchaseDetails; } - final InAppPurchaseAndroidPlatformAddition addition = - InAppPurchasePlatformAddition.instance! - as InAppPurchaseAndroidPlatformAddition; final BillingResultWrapper billingResult = - await addition.consumePurchase(purchaseDetails); + await (InAppPurchasePlatformAddition.instance! + as InAppPurchaseAndroidPlatformAddition) + .consumePurchase(purchaseDetails); final BillingResponse consumedResponse = billingResult.responseCode; if (consumedResponse != BillingResponse.ok) { purchaseDetails.status = PurchaseStatus.error; From 51bb55afa717b00d03c029569e31e1a27d520136 Mon Sep 17 00:00:00 2001 From: "jakub.walusiak" Date: Sat, 15 Oct 2022 17:37:47 +0200 Subject: [PATCH 006/130] [in_app_purchases_android_platform] Reformat billing_client_manager_test.dart and fix imports --- .../lib/src/billing_client_wrappers/sku_details_wrapper.dart | 3 ++- .../billing_client_wrappers/billing_client_manager_test.dart | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/sku_details_wrapper.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/sku_details_wrapper.dart index 5ef13c2e2de2..2689cf37eac4 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/sku_details_wrapper.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/sku_details_wrapper.dart @@ -5,7 +5,8 @@ import 'package:flutter/foundation.dart'; import 'package:json_annotation/json_annotation.dart'; -import '../../billing_client_wrappers.dart'; +import 'billing_client_manager.dart'; +import 'billing_client_wrapper.dart'; // WARNING: Changes to `@JsonSerializable` classes need to be reflected in the // below generated file. Run `flutter packages pub run build_runner watch` to diff --git a/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/billing_client_manager_test.dart b/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/billing_client_manager_test.dart index b329dd8d51f4..8351395cb46b 100644 --- a/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/billing_client_manager_test.dart +++ b/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/billing_client_manager_test.dart @@ -49,6 +49,7 @@ void main() { test('connects on initialization', () { expect(stubPlatform.countPreviousCalls(startConnectionCall), equals(1)); }); + test('waits for connection before executing the operations', () { bool runCalled = false; bool runRawCalled = false; @@ -63,6 +64,7 @@ void main() { expect(runCalled, equals(true)); expect(runRawCalled, equals(true)); }); + test('re-connects when client sends onBillingServiceDisconnected', () { connectedCompleter.complete(); manager.client.callHandler( @@ -71,6 +73,7 @@ void main() { ); expect(stubPlatform.countPreviousCalls(startConnectionCall), equals(2)); }); + test( 're-connects when operation returns BillingResponse.serviceDisconnected', () async { @@ -91,6 +94,7 @@ void main() { expect(result.responseCode, equals(BillingResponse.ok)); }, ); + test('does not re-connect when disposed', () { connectedCompleter.complete(); manager.dispose(); From 213ec8aab82cebd9f79bed66aa81ee97e66d1325 Mon Sep 17 00:00:00 2001 From: Jakub Walusiak Date: Tue, 4 Oct 2022 11:32:22 +0200 Subject: [PATCH 007/130] [in_app_purchases_android_platform] Enhance documentation, move HasBillingResponse --- .../billing_client_manager.dart | 13 +++++++++++-- .../billing_client_wrapper.dart | 6 ++++++ .../billing_client_wrappers/purchase_wrapper.dart | 1 + .../sku_details_wrapper.dart | 10 +--------- .../lib/src/in_app_purchase_android_platform.dart | 7 +++---- 5 files changed, 22 insertions(+), 15 deletions(-) diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_manager.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_manager.dart index 645be67eb0ef..4427032ef910 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_manager.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_manager.dart @@ -8,7 +8,13 @@ import 'package:flutter/widgets.dart'; import 'billing_client_wrapper.dart'; import 'purchase_wrapper.dart'; -import 'sku_details_wrapper.dart'; + +/// Abstraction of result of [BillingClient] operation that includes +/// a [BillingResponse]. +abstract class HasBillingResponse { + /// The status of the operation. + abstract final BillingResponse responseCode; +} /// Utility class that manages a [BillingClient] connection. /// @@ -38,7 +44,6 @@ class BillingClientManager { /// [BillingClient] instance managed by this [BillingClientManager]. /// - /// This field should not be accessed outside of test code. /// In order to access the [BillingClient], consider using [run] and [runRaw] /// methods. @visibleForTesting @@ -59,6 +64,7 @@ class BillingClientManager { /// If given [block] returns [BillingResponse.serviceDisconnected], it will /// be transparently retried after the connection is restored. Because /// of this, [block] may be called multiple times. + /// /// A response with [BillingResponse.serviceDisconnected] may be returned /// in case of [dispose] being called during the operation. /// @@ -96,6 +102,9 @@ class BillingClientManager { /// Ends connection to the [BillingClient]. /// + /// Consider calling [dispose] after you no longer need the [BillingClient] + /// API to free up the resources. + /// /// After calling [dispose] : /// - Further connection attempts will not be made; /// - [purchasesUpdatedStream] will be closed; diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_wrapper.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_wrapper.dart index b64eaab49a9d..042ff9026cf0 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_wrapper.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_wrapper.dart @@ -51,6 +51,12 @@ typedef PurchasesUpdatedListener = void Function( /// `com.android.billingclient.api.BillingClient` API as much as possible, with /// some minor changes to account for language differences. Callbacks have been /// converted to futures where appropriate. +/// +/// Connection to [BillingClient] may be lost at any time (see +/// `onBillingServiceDisconnected` param of [startConnection] and +/// [BillingResponse.serviceDisconnected]). +/// Consider using [BillingClientManager] that handles these disconnections +/// transparently. class BillingClient { /// Creates a billing client. BillingClient(PurchasesUpdatedListener onPurchasesUpdated) { diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/purchase_wrapper.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/purchase_wrapper.dart index a231e4f1bb54..633aa732165b 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/purchase_wrapper.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/purchase_wrapper.dart @@ -6,6 +6,7 @@ import 'package:flutter/foundation.dart'; import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart'; import 'package:json_annotation/json_annotation.dart'; +import 'billing_client_manager.dart'; import 'billing_client_wrapper.dart'; import 'sku_details_wrapper.dart'; diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/sku_details_wrapper.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/sku_details_wrapper.dart index 129a63df67d6..5ef13c2e2de2 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/sku_details_wrapper.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/sku_details_wrapper.dart @@ -3,10 +3,9 @@ // found in the LICENSE file. import 'package:flutter/foundation.dart'; -import 'package:in_app_purchase_android/billing_client_wrappers.dart'; import 'package:json_annotation/json_annotation.dart'; -import 'billing_client_wrapper.dart'; +import '../../billing_client_wrappers.dart'; // WARNING: Changes to `@JsonSerializable` classes need to be reflected in the // below generated file. Run `flutter packages pub run build_runner watch` to @@ -20,13 +19,6 @@ part 'sku_details_wrapper.g.dart'; const String kInvalidBillingResultErrorMessage = 'Invalid billing result map from method channel.'; -/// Abstraction of result of [BillingClient] operation that includes -/// a [BillingResponse]. -abstract class HasBillingResponse { - /// The status of the operation. - abstract final BillingResponse responseCode; -} - /// Dart wrapper around [`com.android.billingclient.api.SkuDetails`](https://developer.android.com/reference/com/android/billingclient/api/SkuDetails). /// /// Contains the details of an available product in Google Play Billing. diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform.dart index 91245574381f..6349842d5f66 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform.dart @@ -233,11 +233,10 @@ class InAppPurchaseAndroidPlatform extends InAppPurchasePlatform { return purchaseDetails; } - final InAppPurchaseAndroidPlatformAddition addition = - InAppPurchasePlatformAddition.instance! - as InAppPurchaseAndroidPlatformAddition; final BillingResultWrapper billingResult = - await addition.consumePurchase(purchaseDetails); + await (InAppPurchasePlatformAddition.instance! + as InAppPurchaseAndroidPlatformAddition) + .consumePurchase(purchaseDetails); final BillingResponse consumedResponse = billingResult.responseCode; if (consumedResponse != BillingResponse.ok) { purchaseDetails.status = PurchaseStatus.error; From c4684bb7bb80d17ea5147204ab2c034d505968b2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 19 Jan 2023 10:04:28 -0800 Subject: [PATCH 008/130] [video_player]: Bump mockito-core (#6974) Bumps [mockito-core](https://github.com/mockito/mockito) from 4.7.0 to 5.0.0. - [Release notes](https://github.com/mockito/mockito/releases) - [Commits](https://github.com/mockito/mockito/compare/v4.7.0...v5.0.0) --- updated-dependencies: - dependency-name: org.mockito:mockito-core dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../video_player/video_player/example/android/app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/video_player/video_player/example/android/app/build.gradle b/packages/video_player/video_player/example/android/app/build.gradle index 338eeb8944f7..8b2086b6c05e 100644 --- a/packages/video_player/video_player/example/android/app/build.gradle +++ b/packages/video_player/video_player/example/android/app/build.gradle @@ -60,7 +60,7 @@ flutter { dependencies { testImplementation 'junit:junit:4.13.2' testImplementation 'org.robolectric:robolectric:4.8.1' - testImplementation 'org.mockito:mockito-core:4.7.0' + testImplementation 'org.mockito:mockito-core:5.0.0' androidTestImplementation 'androidx.test:runner:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' } From d348d445c277d98f2a17a50c7151e128b7f36f58 Mon Sep 17 00:00:00 2001 From: stuartmorgan Date: Thu, 19 Jan 2023 10:05:05 -0800 Subject: [PATCH 009/130] [path_provider] Switch to `path_provider_foundation` (#6989) * [path_provider] Switch to `path_provider_foundation` Switches to using the new combined `path_provider_foundation` for iOS and macOS. Also updates the code documentation to make it less Android and iOS specific. The original goal was to make the documentation for the download directory not be actively wrong for the new implementation, but it seemed like a good time to fix 76427 more generally. (The fact that the docs are kind of a mess because the API itself is kind of a mess is now https://github.com/flutter/flutter/issues/118712.) Fixes flutter/flutter#117941 Fixes https://github.com/flutter/flutter/issues/76427 * Remove exclusion * Update test expectations and README * Update test expectations again, and update docs --- .../path_provider/path_provider/CHANGELOG.md | 4 +- .../path_provider/path_provider/README.md | 2 +- .../integration_test/path_provider_test.dart | 15 ++- .../path_provider/lib/path_provider.dart | 96 ++++++++++--------- .../path_provider/path_provider/pubspec.yaml | 11 +-- script/configs/exclude_all_plugins_app.yaml | 5 - 6 files changed, 67 insertions(+), 66 deletions(-) diff --git a/packages/path_provider/path_provider/CHANGELOG.md b/packages/path_provider/path_provider/CHANGELOG.md index 436523551924..70916ae48ead 100644 --- a/packages/path_provider/path_provider/CHANGELOG.md +++ b/packages/path_provider/path_provider/CHANGELOG.md @@ -1,5 +1,7 @@ -## NEXT +## 2.0.12 +* Switches to the new `path_provider_foundation` implementation package + for iOS and macOS. * Updates code for `no_leading_underscores_for_local_identifiers` lint. * Updates minimum Flutter version to 2.10. * Fixes avoid_redundant_argument_values lint warnings and minor typos. diff --git a/packages/path_provider/path_provider/README.md b/packages/path_provider/path_provider/README.md index 3a52e3e72050..6a954d2ece61 100644 --- a/packages/path_provider/path_provider/README.md +++ b/packages/path_provider/path_provider/README.md @@ -36,7 +36,7 @@ Directories support by platform: | External Storage | ✔️ | ❌ | ❌ | ❌️ | ❌️ | | External Cache Directories | ✔️ | ❌ | ❌ | ❌️ | ❌️ | | External Storage Directories | ✔️ | ❌ | ❌ | ❌️ | ❌️ | -| Downloads | ❌ | ❌ | ✔️ | ✔️ | ✔️ | +| Downloads | ❌ | ✔️ | ✔️ | ✔️ | ✔️ | ## Testing diff --git a/packages/path_provider/path_provider/example/integration_test/path_provider_test.dart b/packages/path_provider/path_provider/example/integration_test/path_provider_test.dart index bf150f66f49b..f59a8faf31e0 100644 --- a/packages/path_provider/path_provider/example/integration_test/path_provider_test.dart +++ b/packages/path_provider/path_provider/example/integration_test/path_provider_test.dart @@ -87,19 +87,16 @@ void main() { } testWidgets('getDownloadsDirectory', (WidgetTester tester) async { - if (Platform.isIOS || Platform.isAndroid) { + if (Platform.isAndroid) { final Future result = getDownloadsDirectory(); expect(result, throwsA(isInstanceOf())); } else { final Directory? result = await getDownloadsDirectory(); - if (Platform.isMacOS) { - // On recent versions of macOS, actually using the downloads directory - // requires a user prompt, so will fail on CI. Instead, just check that - // it returned a path with the expected directory name. - expect(result?.path, endsWith('Downloads')); - } else { - _verifySampleFile(result, 'downloads'); - } + // On recent versions of macOS, actually using the downloads directory + // requires a user prompt (so will fail on CI), and on some platforms the + // directory may not exist. Instead of verifying that it exists, just + // check that it returned a path. + expect(result?.path, isNotEmpty); } }); } diff --git a/packages/path_provider/path_provider/lib/path_provider.dart b/packages/path_provider/path_provider/lib/path_provider.dart index e89d29dc0036..b58a7ff6cc7b 100644 --- a/packages/path_provider/path_provider/lib/path_provider.dart +++ b/packages/path_provider/path_provider/lib/path_provider.dart @@ -45,11 +45,11 @@ PathProviderPlatform get _platform => PathProviderPlatform.instance; /// (and cleaning up) files or directories within this directory. This /// directory is scoped to the calling application. /// -/// On iOS, this uses the `NSCachesDirectory` API. +/// Example implementations: +/// - `NSCachesDirectory` on iOS and macOS. +/// - `Context.getCacheDir` on Android. /// -/// On Android, this uses the `getCacheDir` API on the context. -/// -/// Throws a `MissingPlatformDirectoryException` if the system is unable to +/// Throws a [MissingPlatformDirectoryException] if the system is unable to /// provide the directory. Future getTemporaryDirectory() async { final String? path = await _platform.getTemporaryPath(); @@ -63,15 +63,16 @@ Future getTemporaryDirectory() async { /// Path to a directory where the application may place application support /// files. /// +/// If this directory does not exist, it is created automatically. +/// /// Use this for files you don’t want exposed to the user. Your app should not /// use this directory for user data files. /// -/// On iOS, this uses the `NSApplicationSupportDirectory` API. -/// If this directory does not exist, it is created automatically. -/// -/// On Android, this function uses the `getFilesDir` API on the context. +/// Example implementations: +/// - `NSApplicationSupportDirectory` on iOS and macOS. +/// - The Flutter engine's `PathUtils.getFilesDir` API on Android. /// -/// Throws a `MissingPlatformDirectoryException` if the system is unable to +/// Throws a [MissingPlatformDirectoryException] if the system is unable to /// provide the directory. Future getApplicationSupportDirectory() async { final String? path = await _platform.getApplicationSupportPath(); @@ -86,10 +87,14 @@ Future getApplicationSupportDirectory() async { /// Path to the directory where application can store files that are persistent, /// backed up, and not visible to the user, such as sqlite.db. /// -/// On Android, this function throws an [UnsupportedError] as no equivalent -/// path exists. +/// Example implementations: +/// - `NSApplicationSupportDirectory` on iOS and macOS. +/// +/// Throws an [UnsupportedError] if this is not supported on the current +/// platform. For example, this is unlikely to ever be supported on Android, +/// as no equivalent path exists. /// -/// Throws a `MissingPlatformDirectoryException` if the system is unable to +/// Throws a [MissingPlatformDirectoryException] if the system is unable to /// provide the directory on a supported platform. Future getLibraryDirectory() async { final String? path = await _platform.getLibraryPath(); @@ -102,14 +107,14 @@ Future getLibraryDirectory() async { /// Path to a directory where the application may place data that is /// user-generated, or that cannot otherwise be recreated by your application. /// -/// On iOS, this uses the `NSDocumentDirectory` API. Consider using -/// [getApplicationSupportDirectory] instead if the data is not user-generated. +/// Consider using another path, such as [getApplicationSupportDirectory] or +/// [getExternalStorageDirectory], if the data is not user-generated. /// -/// On Android, this uses the `getDataDirectory` API on the context. Consider -/// using [getExternalStorageDirectory] instead if data is intended to be visible -/// to the user. +/// Example implementations: +/// - `NSDocumentDirectory` on iOS and macOS. +/// - The Flutter engine's `PathUtils.getDataDirectory` API on Android. /// -/// Throws a `MissingPlatformDirectoryException` if the system is unable to +/// Throws a [MissingPlatformDirectoryException] if the system is unable to /// provide the directory. Future getApplicationDocumentsDirectory() async { final String? path = await _platform.getApplicationDocumentsPath(); @@ -121,13 +126,13 @@ Future getApplicationDocumentsDirectory() async { } /// Path to a directory where the application may access top level storage. -/// The current operating system should be determined before issuing this -/// function call, as this functionality is only available on Android. /// -/// On iOS, this function throws an [UnsupportedError] as it is not possible -/// to access outside the app's sandbox. +/// Example implementation: +/// - `getExternalFilesDir(null)` on Android. /// -/// On Android this uses the `getExternalFilesDir(null)`. +/// Throws an [UnsupportedError] if this is not supported on the current +/// platform (for example, on iOS where it is not possible to access outside +/// the app's sandbox). Future getExternalStorageDirectory() async { final String? path = await _platform.getExternalStoragePath(); if (path == null) { @@ -136,19 +141,19 @@ Future getExternalStorageDirectory() async { return Directory(path); } -/// Paths to directories where application specific external cache data can be -/// stored. These paths typically reside on external storage like separate -/// partitions or SD cards. Phones may have multiple storage directories -/// available. +/// Paths to directories where application specific cache data can be stored +/// externally. /// -/// The current operating system should be determined before issuing this -/// function call, as this functionality is only available on Android. +/// These paths typically reside on external storage like separate partitions +/// or SD cards. Phones may have multiple storage directories available. /// -/// On iOS, this function throws an UnsupportedError as it is not possible -/// to access outside the app's sandbox. +/// Example implementation: +/// - Context.getExternalCacheDirs() on Android (or +/// Context.getExternalCacheDir() on API levels below 19). /// -/// On Android this returns Context.getExternalCacheDirs() or -/// Context.getExternalCacheDir() on API levels below 19. +/// Throws an [UnsupportedError] if this is not supported on the current +/// platform. This is unlikely to ever be supported on any platform other than +/// Android. Future?> getExternalCacheDirectories() async { final List? paths = await _platform.getExternalCachePaths(); if (paths == null) { @@ -158,18 +163,19 @@ Future?> getExternalCacheDirectories() async { return paths.map((String path) => Directory(path)).toList(); } -/// Paths to directories where application specific data can be stored. +/// Paths to directories where application specific data can be stored +/// externally. +/// /// These paths typically reside on external storage like separate partitions /// or SD cards. Phones may have multiple storage directories available. /// -/// The current operating system should be determined before issuing this -/// function call, as this functionality is only available on Android. +/// Example implementation: +/// - Context.getExternalFilesDirs(type) on Android (or +/// Context.getExternalFilesDir(type) on API levels below 19). /// -/// On iOS, this function throws an UnsupportedError as it is not possible -/// to access outside the app's sandbox. -/// -/// On Android this returns Context.getExternalFilesDirs(String type) or -/// Context.getExternalFilesDir(String type) on API levels below 19. +/// Throws an [UnsupportedError] if this is not supported on the current +/// platform. This is unlikely to ever be supported on any platform other than +/// Android. Future?> getExternalStorageDirectories({ /// Optional parameter. See [StorageDirectory] for more informations on /// how this type translates to Android storage directories. @@ -185,10 +191,12 @@ Future?> getExternalStorageDirectories({ } /// Path to the directory where downloaded files can be stored. -/// This is typically only relevant on desktop operating systems. /// -/// On Android and on iOS, this function throws an [UnsupportedError] as no equivalent -/// path exists. +/// The returned directory is not guaranteed to exist, so clients should verify +/// that it does before using it, and potentially create it if necessary. +/// +/// Throws an [UnsupportedError] if this is not supported on the current +/// platform. Future getDownloadsDirectory() async { final String? path = await _platform.getDownloadsPath(); if (path == null) { diff --git a/packages/path_provider/path_provider/pubspec.yaml b/packages/path_provider/path_provider/pubspec.yaml index 8b68e1264fe7..ad39a3b16173 100644 --- a/packages/path_provider/path_provider/pubspec.yaml +++ b/packages/path_provider/path_provider/pubspec.yaml @@ -2,7 +2,7 @@ name: path_provider description: Flutter plugin for getting commonly used locations on host platform file systems, such as the temp and app data directories. repository: https://github.com/flutter/plugins/tree/main/packages/path_provider/path_provider issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+path_provider%22 -version: 2.0.11 +version: 2.0.12 environment: sdk: ">=2.14.0 <3.0.0" @@ -14,11 +14,11 @@ flutter: android: default_package: path_provider_android ios: - default_package: path_provider_ios - macos: - default_package: path_provider_macos + default_package: path_provider_foundation linux: default_package: path_provider_linux + macos: + default_package: path_provider_foundation windows: default_package: path_provider_windows @@ -26,9 +26,8 @@ dependencies: flutter: sdk: flutter path_provider_android: ^2.0.6 - path_provider_ios: ^2.0.6 + path_provider_foundation: ^2.1.0 path_provider_linux: ^2.0.1 - path_provider_macos: ^2.0.0 path_provider_platform_interface: ^2.0.0 path_provider_windows: ^2.0.2 diff --git a/script/configs/exclude_all_plugins_app.yaml b/script/configs/exclude_all_plugins_app.yaml index c116402919b4..8dd0fde5ef5f 100644 --- a/script/configs/exclude_all_plugins_app.yaml +++ b/script/configs/exclude_all_plugins_app.yaml @@ -8,8 +8,3 @@ # This is a permament entry, as it should never be a direct app dependency. - plugin_platform_interface -# Temporarily excluded to avoid runtime conflicts with -# path_provider_macos and _ios, which are still what path_provider -# uses. This will be removed when the switch to path_provider_foundation -# is complete. See https://github.com/flutter/flutter/issues/117941 -- path_provider_foundation From dc2e4a0e34eb06108b43029368e51705ae6204fa Mon Sep 17 00:00:00 2001 From: Rexios Date: Thu, 19 Jan 2023 13:07:17 -0500 Subject: [PATCH 010/130] [video_player] Expose `VideoScrubber` so it can be used to create custom video timelines (#6680) * Expose `VideoScrubber` so it can be used to make custom video timelines * Added documentation * Updated version * Cleanup * Formatting * Update CHANGELOG.md --- .../video_player/video_player/CHANGELOG.md | 4 ++++ .../video_player/lib/video_player.dart | 21 +++++++++++++------ .../video_player/video_player/pubspec.yaml | 2 +- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/packages/video_player/video_player/CHANGELOG.md b/packages/video_player/video_player/CHANGELOG.md index c91694627bb8..fdf73487c076 100644 --- a/packages/video_player/video_player/CHANGELOG.md +++ b/packages/video_player/video_player/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.5.0 + +* Exposes `VideoScrubber` so it can be used to make custom video progress indicators + ## 2.4.10 * Adds compatibilty with version 6.0 of the platform interface. diff --git a/packages/video_player/video_player/lib/video_player.dart b/packages/video_player/video_player/lib/video_player.dart index 76f865aa7359..f27c34210466 100644 --- a/packages/video_player/video_player/lib/video_player.dart +++ b/packages/video_player/video_player/lib/video_player.dart @@ -835,20 +835,29 @@ class VideoProgressColors { final Color backgroundColor; } -class _VideoScrubber extends StatefulWidget { - const _VideoScrubber({ +/// A scrubber to control [VideoPlayerController]s +class VideoScrubber extends StatefulWidget { + /// Create a [VideoScrubber] handler with the given [child]. + /// + /// [controller] is the [VideoPlayerController] that will be controlled by + /// this scrubber. + const VideoScrubber({ + Key? key, required this.child, required this.controller, - }); + }) : super(key: key); + /// The widget that will be displayed inside the gesture detector. final Widget child; + + /// The [VideoPlayerController] that will be controlled by this scrubber. final VideoPlayerController controller; @override - _VideoScrubberState createState() => _VideoScrubberState(); + State createState() => _VideoScrubberState(); } -class _VideoScrubberState extends State<_VideoScrubber> { +class _VideoScrubberState extends State { bool _controllerWasPlaying = false; VideoPlayerController get controller => widget.controller; @@ -1013,7 +1022,7 @@ class _VideoProgressIndicatorState extends State { child: progressIndicator, ); if (widget.allowScrubbing) { - return _VideoScrubber( + return VideoScrubber( controller: controller, child: paddedProgressIndicator, ); diff --git a/packages/video_player/video_player/pubspec.yaml b/packages/video_player/video_player/pubspec.yaml index a049f0bc770e..0f37c0328df2 100644 --- a/packages/video_player/video_player/pubspec.yaml +++ b/packages/video_player/video_player/pubspec.yaml @@ -3,7 +3,7 @@ description: Flutter plugin for displaying inline video with other Flutter widgets on Android, iOS, and web. repository: https://github.com/flutter/plugins/tree/main/packages/video_player/video_player issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+video_player%22 -version: 2.4.10 +version: 2.5.0 environment: sdk: ">=2.14.0 <3.0.0" From 5f44021a181809ee1298919ce3d09347d53efb7f Mon Sep 17 00:00:00 2001 From: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> Date: Thu, 19 Jan 2023 16:49:30 -0500 Subject: [PATCH 011/130] [webview_flutter_android] Adds support for receiving Java callback `WebChromeClient.onShowFileChooser` (#6881) * some progress * more work * compiling code * dart side should be correct * maybe working code * fix plugin class * formatting * some docs and polish * foramtting tests and docs * version bump * doc improvements * flutterapi create test * working java test * unused imports * formatting and more tests * formatting and more tests * formatting * copy and tests * add more description to the custom method * formatting * change doc wording * more docs * null out result early * remove the open option * fix spelling * interface implementation * version bump * move webchromeclient to webview controller * tests * reference method in changelong * missing typed data import * undo changes * use stateerror instead * updated lints --- .../webview_flutter_android/CHANGELOG.md | 5 + .../FileChooserParamsFlutterApiImpl.java | 74 ++ .../GeneratedAndroidWebView.java | 675 +++++++--- .../WebChromeClientFlutterApiImpl.java | 27 + .../WebChromeClientHostApiImpl.java | 42 + .../webviewflutter/FileChooserParamsTest.java | 74 ++ .../legacy/webview_flutter_test.dart | 2 +- .../webview_flutter_test.dart | 2 +- .../lib/src/android_navigation_delegate.dart | 318 ----- .../lib/src/android_proxy.dart | 5 + .../lib/src/android_webview.dart | 98 +- .../lib/src/android_webview.pigeon.dart | 1115 +++++++++-------- .../lib/src/android_webview_api_impls.dart | 69 + .../lib/src/android_webview_controller.dart | 419 ++++++- .../lib/src/android_webview_platform.dart | 1 - .../src/legacy/webview_android_widget.dart | 8 +- .../lib/src/weak_reference_utils.dart | 2 +- .../lib/webview_flutter_android.dart | 1 - .../pigeons/android_webview.dart | 54 + .../webview_flutter_android/pubspec.yaml | 4 +- .../android_navigation_delegate_test.dart | 17 +- .../test/android_webview_controller_test.dart | 113 +- ...android_webview_controller_test.mocks.dart | 129 +- .../test/android_webview_test.dart | 58 + .../test/android_webview_test.mocks.dart | 25 + .../webview_android_widget_test.mocks.dart | 10 + .../test/test_android_webview.pigeon.dart | 176 ++- 27 files changed, 2340 insertions(+), 1183 deletions(-) create mode 100644 packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/FileChooserParamsFlutterApiImpl.java create mode 100644 packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/FileChooserParamsTest.java delete mode 100644 packages/webview_flutter/webview_flutter_android/lib/src/android_navigation_delegate.dart diff --git a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md index 37abf3cf2b1b..9821c2c74e2e 100644 --- a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md @@ -1,3 +1,8 @@ +## 3.2.0 + +* Adds support for handling file selection. See `AndroidWebViewController.setOnShowFileSelector`. +* Updates pigeon dev dependency to `4.2.14`. + ## 3.1.3 * Fixes crash when the Java `InstanceManager` was used after plugin was removed from the engine. diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/FileChooserParamsFlutterApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/FileChooserParamsFlutterApiImpl.java new file mode 100644 index 000000000000..679785949697 --- /dev/null +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/FileChooserParamsFlutterApiImpl.java @@ -0,0 +1,74 @@ +// Copyright 2013 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.webviewflutter; + +import android.os.Build; +import android.webkit.WebChromeClient; +import androidx.annotation.RequiresApi; +import io.flutter.plugin.common.BinaryMessenger; +import java.util.Arrays; + +/** + * Flutter Api implementation for {@link android.webkit.WebChromeClient.FileChooserParams}. + * + *

Passes arguments of callbacks methods from a {@link + * android.webkit.WebChromeClient.FileChooserParams} to Dart. + */ +@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) +public class FileChooserParamsFlutterApiImpl + extends GeneratedAndroidWebView.FileChooserParamsFlutterApi { + private final InstanceManager instanceManager; + + /** + * Creates a Flutter api that sends messages to Dart. + * + * @param binaryMessenger handles sending messages to Dart + * @param instanceManager maintains instances stored to communicate with Dart objects + */ + public FileChooserParamsFlutterApiImpl( + BinaryMessenger binaryMessenger, InstanceManager instanceManager) { + super(binaryMessenger); + this.instanceManager = instanceManager; + } + + private static GeneratedAndroidWebView.FileChooserModeEnumData toFileChooserEnumData(int mode) { + final GeneratedAndroidWebView.FileChooserModeEnumData.Builder builder = + new GeneratedAndroidWebView.FileChooserModeEnumData.Builder(); + + switch (mode) { + case WebChromeClient.FileChooserParams.MODE_OPEN: + builder.setValue(GeneratedAndroidWebView.FileChooserMode.OPEN); + break; + case WebChromeClient.FileChooserParams.MODE_OPEN_MULTIPLE: + builder.setValue(GeneratedAndroidWebView.FileChooserMode.OPEN_MULTIPLE); + break; + case WebChromeClient.FileChooserParams.MODE_SAVE: + builder.setValue(GeneratedAndroidWebView.FileChooserMode.SAVE); + break; + default: + throw new IllegalArgumentException(String.format("Unsupported FileChooserMode: %d", mode)); + } + + return builder.build(); + } + + /** + * Stores the FileChooserParams instance and notifies Dart to create a new FileChooserParams + * instance that is attached to this one. + * + * @return the instanceId of the stored instance + */ + public long create(WebChromeClient.FileChooserParams instance, Reply callback) { + final long instanceId = instanceManager.addHostCreatedInstance(instance); + create( + instanceId, + instance.isCaptureEnabled(), + Arrays.asList(instance.getAcceptTypes()), + toFileChooserEnumData(instance.getMode()), + instance.getFilenameHint(), + callback); + return instanceId; + } +} diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java index 15c80cc0a907..425f6c1415bd 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java @@ -1,7 +1,7 @@ // Copyright 2013 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. -// Autogenerated from Pigeon (v4.2.3), do not edit directly. +// Autogenerated from Pigeon (v4.2.14), do not edit directly. // See also: https://pub.dev/packages/pigeon package io.flutter.plugins.webviewflutter; @@ -18,7 +18,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Map; @@ -26,6 +25,90 @@ @SuppressWarnings({"unused", "unchecked", "CodeBlock2Expr", "RedundantSuppression"}) public class GeneratedAndroidWebView { + /** + * Mode of how to select files for a file chooser. + * + *

See + * https://developer.android.com/reference/android/webkit/WebChromeClient.FileChooserParams. + */ + public enum FileChooserMode { + /** + * Open single file and requires that the file exists before allowing the user to pick it. + * + *

See + * https://developer.android.com/reference/android/webkit/WebChromeClient.FileChooserParams#MODE_OPEN. + */ + OPEN(0), + /** + * Similar to [open] but allows multiple files to be selected. + * + *

See + * https://developer.android.com/reference/android/webkit/WebChromeClient.FileChooserParams#MODE_OPEN_MULTIPLE. + */ + OPEN_MULTIPLE(1), + /** + * Allows picking a nonexistent file and saving it. + * + *

See + * https://developer.android.com/reference/android/webkit/WebChromeClient.FileChooserParams#MODE_SAVE. + */ + SAVE(2); + + private final int index; + + private FileChooserMode(final int index) { + this.index = index; + } + } + + /** Generated class from Pigeon that represents data sent in messages. */ + public static class FileChooserModeEnumData { + private @NonNull FileChooserMode value; + + public @NonNull FileChooserMode getValue() { + return value; + } + + public void setValue(@NonNull FileChooserMode setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"value\" is null."); + } + this.value = setterArg; + } + + /** Constructor is private to enforce null safety; use Builder. */ + private FileChooserModeEnumData() {} + + public static final class Builder { + private @Nullable FileChooserMode value; + + public @NonNull Builder setValue(@NonNull FileChooserMode setterArg) { + this.value = setterArg; + return this; + } + + public @NonNull FileChooserModeEnumData build() { + FileChooserModeEnumData pigeonReturn = new FileChooserModeEnumData(); + pigeonReturn.setValue(value); + return pigeonReturn; + } + } + + @NonNull + ArrayList toList() { + ArrayList toListResult = new ArrayList(1); + toListResult.add(value == null ? null : value.index); + return toListResult; + } + + static @NonNull FileChooserModeEnumData fromList(@NonNull ArrayList list) { + FileChooserModeEnumData pigeonResult = new FileChooserModeEnumData(); + Object value = list.get(0); + pigeonResult.setValue(value == null ? null : FileChooserMode.values()[(int) value]); + return pigeonResult; + } + } + /** Generated class from Pigeon that represents data sent in messages. */ public static class WebResourceRequestData { private @NonNull String url; @@ -162,30 +245,30 @@ public static final class Builder { } @NonNull - Map toMap() { - Map toMapResult = new HashMap<>(); - toMapResult.put("url", url); - toMapResult.put("isForMainFrame", isForMainFrame); - toMapResult.put("isRedirect", isRedirect); - toMapResult.put("hasGesture", hasGesture); - toMapResult.put("method", method); - toMapResult.put("requestHeaders", requestHeaders); - return toMapResult; - } - - static @NonNull WebResourceRequestData fromMap(@NonNull Map map) { + ArrayList toList() { + ArrayList toListResult = new ArrayList(6); + toListResult.add(url); + toListResult.add(isForMainFrame); + toListResult.add(isRedirect); + toListResult.add(hasGesture); + toListResult.add(method); + toListResult.add(requestHeaders); + return toListResult; + } + + static @NonNull WebResourceRequestData fromList(@NonNull ArrayList list) { WebResourceRequestData pigeonResult = new WebResourceRequestData(); - Object url = map.get("url"); + Object url = list.get(0); pigeonResult.setUrl((String) url); - Object isForMainFrame = map.get("isForMainFrame"); + Object isForMainFrame = list.get(1); pigeonResult.setIsForMainFrame((Boolean) isForMainFrame); - Object isRedirect = map.get("isRedirect"); + Object isRedirect = list.get(2); pigeonResult.setIsRedirect((Boolean) isRedirect); - Object hasGesture = map.get("hasGesture"); + Object hasGesture = list.get(3); pigeonResult.setHasGesture((Boolean) hasGesture); - Object method = map.get("method"); + Object method = list.get(4); pigeonResult.setMethod((String) method); - Object requestHeaders = map.get("requestHeaders"); + Object requestHeaders = list.get(5); pigeonResult.setRequestHeaders((Map) requestHeaders); return pigeonResult; } @@ -246,21 +329,21 @@ public static final class Builder { } @NonNull - Map toMap() { - Map toMapResult = new HashMap<>(); - toMapResult.put("errorCode", errorCode); - toMapResult.put("description", description); - return toMapResult; + ArrayList toList() { + ArrayList toListResult = new ArrayList(2); + toListResult.add(errorCode); + toListResult.add(description); + return toListResult; } - static @NonNull WebResourceErrorData fromMap(@NonNull Map map) { + static @NonNull WebResourceErrorData fromList(@NonNull ArrayList list) { WebResourceErrorData pigeonResult = new WebResourceErrorData(); - Object errorCode = map.get("errorCode"); + Object errorCode = list.get(0); pigeonResult.setErrorCode( (errorCode == null) ? null : ((errorCode instanceof Integer) ? (Integer) errorCode : (Long) errorCode)); - Object description = map.get("description"); + Object description = list.get(1); pigeonResult.setDescription((String) description); return pigeonResult; } @@ -321,18 +404,18 @@ public static final class Builder { } @NonNull - Map toMap() { - Map toMapResult = new HashMap<>(); - toMapResult.put("x", x); - toMapResult.put("y", y); - return toMapResult; + ArrayList toList() { + ArrayList toListResult = new ArrayList(2); + toListResult.add(x); + toListResult.add(y); + return toListResult; } - static @NonNull WebViewPoint fromMap(@NonNull Map map) { + static @NonNull WebViewPoint fromList(@NonNull ArrayList list) { WebViewPoint pigeonResult = new WebViewPoint(); - Object x = map.get("x"); + Object x = list.get(0); pigeonResult.setX((x == null) ? null : ((x instanceof Integer) ? (Integer) x : (Long) x)); - Object y = map.get("y"); + Object y = list.get(1); pigeonResult.setY((y == null) ? null : ((y instanceof Integer) ? (Integer) y : (Long) y)); return pigeonResult; } @@ -370,7 +453,7 @@ static void setup(BinaryMessenger binaryMessenger, JavaObjectHostApi api) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -379,9 +462,10 @@ static void setup(BinaryMessenger binaryMessenger, JavaObjectHostApi api) { throw new NullPointerException("identifierArg unexpectedly null."); } api.dispose((identifierArg == null) ? null : identifierArg.longValue()); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -448,25 +532,25 @@ static void setup(BinaryMessenger binaryMessenger, CookieManagerHostApi api) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { Result resultCallback = new Result() { public void success(Boolean result) { - wrapped.put("result", result); + wrapped.add(0, result); reply.reply(wrapped); } public void error(Throwable error) { - wrapped.put("error", wrapError(error)); - reply.reply(wrapped); + ArrayList wrappedError = wrapError(error); + reply.reply(wrappedError); } }; api.clearCookies(resultCallback); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); - reply.reply(wrapped); + ArrayList wrappedError = wrapError(exception); + reply.reply(wrappedError); } }); } else { @@ -480,7 +564,7 @@ public void error(Throwable error) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -493,9 +577,10 @@ public void error(Throwable error) { throw new NullPointerException("valueArg unexpectedly null."); } api.setCookie(urlArg, valueArg); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -515,7 +600,7 @@ private WebViewHostApiCodec() {} protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) { switch (type) { case (byte) 128: - return WebViewPoint.fromMap((Map) readValue(buffer)); + return WebViewPoint.fromList((ArrayList) readValue(buffer)); default: return super.readValueOfType(type, buffer); @@ -526,7 +611,7 @@ protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) { protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) { if (value instanceof WebViewPoint) { stream.write(128); - writeValue(stream, ((WebViewPoint) value).toMap()); + writeValue(stream, ((WebViewPoint) value).toList()); } else { super.writeValue(stream, value); } @@ -620,7 +705,7 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -635,9 +720,10 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { api.create( (instanceIdArg == null) ? null : instanceIdArg.longValue(), useHybridCompositionArg); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -652,7 +738,7 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -671,9 +757,10 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { dataArg, mimeTypeArg, encodingArg); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -690,7 +777,7 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -713,9 +800,10 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { mimeTypeArg, encodingArg, historyUrlArg); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -730,7 +818,7 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -750,9 +838,10 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { (instanceIdArg == null) ? null : instanceIdArg.longValue(), urlArg, headersArg); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -767,7 +856,7 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -785,9 +874,10 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { } api.postUrl( (instanceIdArg == null) ? null : instanceIdArg.longValue(), urlArg, dataArg); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -802,7 +892,7 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -812,9 +902,10 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { } String output = api.getUrl((instanceIdArg == null) ? null : instanceIdArg.longValue()); - wrapped.put("result", output); + wrapped.add(0, output); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -829,7 +920,7 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -839,9 +930,10 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { } Boolean output = api.canGoBack((instanceIdArg == null) ? null : instanceIdArg.longValue()); - wrapped.put("result", output); + wrapped.add(0, output); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -856,7 +948,7 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -866,9 +958,10 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { } Boolean output = api.canGoForward((instanceIdArg == null) ? null : instanceIdArg.longValue()); - wrapped.put("result", output); + wrapped.add(0, output); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -883,7 +976,7 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -892,9 +985,10 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { throw new NullPointerException("instanceIdArg unexpectedly null."); } api.goBack((instanceIdArg == null) ? null : instanceIdArg.longValue()); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -909,7 +1003,7 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -918,9 +1012,10 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { throw new NullPointerException("instanceIdArg unexpectedly null."); } api.goForward((instanceIdArg == null) ? null : instanceIdArg.longValue()); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -935,7 +1030,7 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -944,9 +1039,10 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { throw new NullPointerException("instanceIdArg unexpectedly null."); } api.reload((instanceIdArg == null) ? null : instanceIdArg.longValue()); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -961,7 +1057,7 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -976,9 +1072,10 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { api.clearCache( (instanceIdArg == null) ? null : instanceIdArg.longValue(), includeDiskFilesArg); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -995,7 +1092,7 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -1010,13 +1107,13 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { Result resultCallback = new Result() { public void success(String result) { - wrapped.put("result", result); + wrapped.add(0, result); reply.reply(wrapped); } public void error(Throwable error) { - wrapped.put("error", wrapError(error)); - reply.reply(wrapped); + ArrayList wrappedError = wrapError(error); + reply.reply(wrappedError); } }; @@ -1025,8 +1122,8 @@ public void error(Throwable error) { javascriptStringArg, resultCallback); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); - reply.reply(wrapped); + ArrayList wrappedError = wrapError(exception); + reply.reply(wrappedError); } }); } else { @@ -1040,7 +1137,7 @@ public void error(Throwable error) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -1050,9 +1147,10 @@ public void error(Throwable error) { } String output = api.getTitle((instanceIdArg == null) ? null : instanceIdArg.longValue()); - wrapped.put("result", output); + wrapped.add(0, output); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -1067,7 +1165,7 @@ public void error(Throwable error) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -1087,9 +1185,10 @@ public void error(Throwable error) { (instanceIdArg == null) ? null : instanceIdArg.longValue(), (xArg == null) ? null : xArg.longValue(), (yArg == null) ? null : yArg.longValue()); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -1104,7 +1203,7 @@ public void error(Throwable error) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -1124,9 +1223,10 @@ public void error(Throwable error) { (instanceIdArg == null) ? null : instanceIdArg.longValue(), (xArg == null) ? null : xArg.longValue(), (yArg == null) ? null : yArg.longValue()); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -1141,7 +1241,7 @@ public void error(Throwable error) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -1151,9 +1251,10 @@ public void error(Throwable error) { } Long output = api.getScrollX((instanceIdArg == null) ? null : instanceIdArg.longValue()); - wrapped.put("result", output); + wrapped.add(0, output); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -1168,7 +1269,7 @@ public void error(Throwable error) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -1178,9 +1279,10 @@ public void error(Throwable error) { } Long output = api.getScrollY((instanceIdArg == null) ? null : instanceIdArg.longValue()); - wrapped.put("result", output); + wrapped.add(0, output); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -1195,7 +1297,7 @@ public void error(Throwable error) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -1206,9 +1308,10 @@ public void error(Throwable error) { WebViewPoint output = api.getScrollPosition( (instanceIdArg == null) ? null : instanceIdArg.longValue()); - wrapped.put("result", output); + wrapped.add(0, output); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -1225,7 +1328,7 @@ public void error(Throwable error) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -1234,9 +1337,10 @@ public void error(Throwable error) { throw new NullPointerException("enabledArg unexpectedly null."); } api.setWebContentsDebuggingEnabled(enabledArg); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -1251,7 +1355,7 @@ public void error(Throwable error) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -1268,9 +1372,10 @@ public void error(Throwable error) { (webViewClientInstanceIdArg == null) ? null : webViewClientInstanceIdArg.longValue()); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -1287,7 +1392,7 @@ public void error(Throwable error) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -1305,9 +1410,10 @@ public void error(Throwable error) { (javaScriptChannelInstanceIdArg == null) ? null : javaScriptChannelInstanceIdArg.longValue()); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -1324,7 +1430,7 @@ public void error(Throwable error) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -1342,9 +1448,10 @@ public void error(Throwable error) { (javaScriptChannelInstanceIdArg == null) ? null : javaScriptChannelInstanceIdArg.longValue()); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -1361,7 +1468,7 @@ public void error(Throwable error) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -1373,9 +1480,10 @@ public void error(Throwable error) { api.setDownloadListener( (instanceIdArg == null) ? null : instanceIdArg.longValue(), (listenerInstanceIdArg == null) ? null : listenerInstanceIdArg.longValue()); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -1392,7 +1500,7 @@ public void error(Throwable error) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -1404,9 +1512,10 @@ public void error(Throwable error) { api.setWebChromeClient( (instanceIdArg == null) ? null : instanceIdArg.longValue(), (clientInstanceIdArg == null) ? null : clientInstanceIdArg.longValue()); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -1423,7 +1532,7 @@ public void error(Throwable error) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -1438,9 +1547,10 @@ public void error(Throwable error) { api.setBackgroundColor( (instanceIdArg == null) ? null : instanceIdArg.longValue(), (colorArg == null) ? null : colorArg.longValue()); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -1493,7 +1603,7 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -1508,9 +1618,10 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { api.create( (instanceIdArg == null) ? null : instanceIdArg.longValue(), (webViewInstanceIdArg == null) ? null : webViewInstanceIdArg.longValue()); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -1527,7 +1638,7 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -1541,9 +1652,10 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { } api.setDomStorageEnabled( (instanceIdArg == null) ? null : instanceIdArg.longValue(), flagArg); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -1560,7 +1672,7 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -1574,9 +1686,10 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { } api.setJavaScriptCanOpenWindowsAutomatically( (instanceIdArg == null) ? null : instanceIdArg.longValue(), flagArg); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -1593,7 +1706,7 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -1607,9 +1720,10 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { } api.setSupportMultipleWindows( (instanceIdArg == null) ? null : instanceIdArg.longValue(), supportArg); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -1626,7 +1740,7 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -1640,9 +1754,10 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { } api.setJavaScriptEnabled( (instanceIdArg == null) ? null : instanceIdArg.longValue(), flagArg); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -1659,7 +1774,7 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -1671,9 +1786,10 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { api.setUserAgentString( (instanceIdArg == null) ? null : instanceIdArg.longValue(), userAgentStringArg); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -1690,7 +1806,7 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -1704,9 +1820,10 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { } api.setMediaPlaybackRequiresUserGesture( (instanceIdArg == null) ? null : instanceIdArg.longValue(), requireArg); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -1723,7 +1840,7 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -1737,9 +1854,10 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { } api.setSupportZoom( (instanceIdArg == null) ? null : instanceIdArg.longValue(), supportArg); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -1756,7 +1874,7 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -1770,9 +1888,10 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { } api.setLoadWithOverviewMode( (instanceIdArg == null) ? null : instanceIdArg.longValue(), overviewArg); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -1789,7 +1908,7 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -1803,9 +1922,10 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { } api.setUseWideViewPort( (instanceIdArg == null) ? null : instanceIdArg.longValue(), useArg); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -1822,7 +1942,7 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -1836,9 +1956,10 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { } api.setDisplayZoomControls( (instanceIdArg == null) ? null : instanceIdArg.longValue(), enabledArg); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -1855,7 +1976,7 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -1869,9 +1990,10 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { } api.setBuiltInZoomControls( (instanceIdArg == null) ? null : instanceIdArg.longValue(), enabledArg); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -1888,7 +2010,7 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -1902,9 +2024,10 @@ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { } api.setAllowFileAccess( (instanceIdArg == null) ? null : instanceIdArg.longValue(), enabledArg); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -1934,7 +2057,7 @@ static void setup(BinaryMessenger binaryMessenger, JavaScriptChannelHostApi api) if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -1948,9 +2071,10 @@ static void setup(BinaryMessenger binaryMessenger, JavaScriptChannelHostApi api) } api.create( (instanceIdArg == null) ? null : instanceIdArg.longValue(), channelNameArg); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -2013,7 +2137,7 @@ static void setup(BinaryMessenger binaryMessenger, WebViewClientHostApi api) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -2022,9 +2146,10 @@ static void setup(BinaryMessenger binaryMessenger, WebViewClientHostApi api) { throw new NullPointerException("instanceIdArg unexpectedly null."); } api.create((instanceIdArg == null) ? null : instanceIdArg.longValue()); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -2041,7 +2166,7 @@ static void setup(BinaryMessenger binaryMessenger, WebViewClientHostApi api) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -2055,9 +2180,10 @@ static void setup(BinaryMessenger binaryMessenger, WebViewClientHostApi api) { } api.setSynchronousReturnValueForShouldOverrideUrlLoading( (instanceIdArg == null) ? null : instanceIdArg.longValue(), valueArg); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -2077,10 +2203,10 @@ private WebViewClientFlutterApiCodec() {} protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) { switch (type) { case (byte) 128: - return WebResourceErrorData.fromMap((Map) readValue(buffer)); + return WebResourceErrorData.fromList((ArrayList) readValue(buffer)); case (byte) 129: - return WebResourceRequestData.fromMap((Map) readValue(buffer)); + return WebResourceRequestData.fromList((ArrayList) readValue(buffer)); default: return super.readValueOfType(type, buffer); @@ -2091,10 +2217,10 @@ protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) { protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) { if (value instanceof WebResourceErrorData) { stream.write(128); - writeValue(stream, ((WebResourceErrorData) value).toMap()); + writeValue(stream, ((WebResourceErrorData) value).toList()); } else if (value instanceof WebResourceRequestData) { stream.write(129); - writeValue(stream, ((WebResourceRequestData) value).toMap()); + writeValue(stream, ((WebResourceRequestData) value).toList()); } else { super.writeValue(stream, value); } @@ -2247,7 +2373,7 @@ static void setup(BinaryMessenger binaryMessenger, DownloadListenerHostApi api) if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -2256,9 +2382,10 @@ static void setup(BinaryMessenger binaryMessenger, DownloadListenerHostApi api) throw new NullPointerException("instanceIdArg unexpectedly null."); } api.create((instanceIdArg == null) ? null : instanceIdArg.longValue()); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -2315,6 +2442,9 @@ public void onDownloadStart( public interface WebChromeClientHostApi { void create(@NonNull Long instanceId); + void setSynchronousReturnValueForOnShowFileChooser( + @NonNull Long instanceId, @NonNull Boolean value); + /** The codec used by WebChromeClientHostApi. */ static MessageCodec getCodec() { return new StandardMessageCodec(); @@ -2331,7 +2461,7 @@ static void setup(BinaryMessenger binaryMessenger, WebChromeClientHostApi api) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -2340,9 +2470,44 @@ static void setup(BinaryMessenger binaryMessenger, WebChromeClientHostApi api) { throw new NullPointerException("instanceIdArg unexpectedly null."); } api.create((instanceIdArg == null) ? null : instanceIdArg.longValue()); - wrapped.put("result", null); + wrapped.add(0, null); + } catch (Error | RuntimeException exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.WebChromeClientHostApi.setSynchronousReturnValueForOnShowFileChooser", + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList<>(); + try { + ArrayList args = (ArrayList) message; + assert args != null; + Number instanceIdArg = (Number) args.get(0); + if (instanceIdArg == null) { + throw new NullPointerException("instanceIdArg unexpectedly null."); + } + Boolean valueArg = (Boolean) args.get(1); + if (valueArg == null) { + throw new NullPointerException("valueArg unexpectedly null."); + } + api.setSynchronousReturnValueForOnShowFileChooser( + (instanceIdArg == null) ? null : instanceIdArg.longValue(), valueArg); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -2376,7 +2541,7 @@ static void setup(BinaryMessenger binaryMessenger, FlutterAssetManagerHostApi ap if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -2385,9 +2550,10 @@ static void setup(BinaryMessenger binaryMessenger, FlutterAssetManagerHostApi ap throw new NullPointerException("pathArg unexpectedly null."); } List output = api.list(pathArg); - wrapped.put("result", output); + wrapped.add(0, output); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -2404,7 +2570,7 @@ static void setup(BinaryMessenger binaryMessenger, FlutterAssetManagerHostApi ap if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -2413,9 +2579,10 @@ static void setup(BinaryMessenger binaryMessenger, FlutterAssetManagerHostApi ap throw new NullPointerException("nameArg unexpectedly null."); } String output = api.getAssetFilePathByName(nameArg); - wrapped.put("result", output); + wrapped.add(0, output); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -2457,6 +2624,26 @@ public void onProgressChanged( callback.reply(null); }); } + + public void onShowFileChooser( + @NonNull Long instanceIdArg, + @NonNull Long webViewInstanceIdArg, + @NonNull Long paramsInstanceIdArg, + Reply> callback) { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.WebChromeClientFlutterApi.onShowFileChooser", + getCodec()); + channel.send( + new ArrayList( + Arrays.asList(instanceIdArg, webViewInstanceIdArg, paramsInstanceIdArg)), + channelReply -> { + @SuppressWarnings("ConstantConditions") + List output = (List) channelReply; + callback.reply(output); + }); + } } /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ public interface WebStorageHostApi { @@ -2479,7 +2666,7 @@ static void setup(BinaryMessenger binaryMessenger, WebStorageHostApi api) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -2488,9 +2675,10 @@ static void setup(BinaryMessenger binaryMessenger, WebStorageHostApi api) { throw new NullPointerException("instanceIdArg unexpectedly null."); } api.create((instanceIdArg == null) ? null : instanceIdArg.longValue()); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -2505,7 +2693,7 @@ static void setup(BinaryMessenger binaryMessenger, WebStorageHostApi api) { if (api != null) { channel.setMessageHandler( (message, reply) -> { - Map wrapped = new HashMap<>(); + ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; @@ -2514,9 +2702,10 @@ static void setup(BinaryMessenger binaryMessenger, WebStorageHostApi api) { throw new NullPointerException("instanceIdArg unexpectedly null."); } api.deleteAllData((instanceIdArg == null) ? null : instanceIdArg.longValue()); - wrapped.put("result", null); + wrapped.add(0, null); } catch (Error | RuntimeException exception) { - wrapped.put("error", wrapError(exception)); + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; } reply.reply(wrapped); }); @@ -2527,14 +2716,84 @@ static void setup(BinaryMessenger binaryMessenger, WebStorageHostApi api) { } } + private static class FileChooserParamsFlutterApiCodec extends StandardMessageCodec { + public static final FileChooserParamsFlutterApiCodec INSTANCE = + new FileChooserParamsFlutterApiCodec(); + + private FileChooserParamsFlutterApiCodec() {} + + @Override + protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) { + switch (type) { + case (byte) 128: + return FileChooserModeEnumData.fromList((ArrayList) readValue(buffer)); + + default: + return super.readValueOfType(type, buffer); + } + } + + @Override + protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) { + if (value instanceof FileChooserModeEnumData) { + stream.write(128); + writeValue(stream, ((FileChooserModeEnumData) value).toList()); + } else { + super.writeValue(stream, value); + } + } + } + + /** + * Handles callbacks methods for the native Java FileChooserParams class. + * + *

See + * https://developer.android.com/reference/android/webkit/WebChromeClient.FileChooserParams. + * + *

Generated class from Pigeon that represents Flutter messages that can be called from Java. + */ + public static class FileChooserParamsFlutterApi { + private final BinaryMessenger binaryMessenger; + + public FileChooserParamsFlutterApi(BinaryMessenger argBinaryMessenger) { + this.binaryMessenger = argBinaryMessenger; + } + + public interface Reply { + void reply(T reply); + } + /** The codec used by FileChooserParamsFlutterApi. */ + static MessageCodec getCodec() { + return FileChooserParamsFlutterApiCodec.INSTANCE; + } + + public void create( + @NonNull Long instanceIdArg, + @NonNull Boolean isCaptureEnabledArg, + @NonNull List acceptTypesArg, + @NonNull FileChooserModeEnumData modeArg, + @Nullable String filenameHintArg, + Reply callback) { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, "dev.flutter.pigeon.FileChooserParamsFlutterApi.create", getCodec()); + channel.send( + new ArrayList( + Arrays.asList( + instanceIdArg, isCaptureEnabledArg, acceptTypesArg, modeArg, filenameHintArg)), + channelReply -> { + callback.reply(null); + }); + } + } + @NonNull - private static Map wrapError(@NonNull Throwable exception) { - Map errorMap = new HashMap<>(); - errorMap.put("message", exception.toString()); - errorMap.put("code", exception.getClass().getSimpleName()); - errorMap.put( - "details", + private static ArrayList wrapError(@NonNull Throwable exception) { + ArrayList errorList = new ArrayList<>(3); + errorList.add(exception.toString()); + errorList.add(exception.getClass().getSimpleName()); + errorList.add( "Cause: " + exception.getCause() + ", Stacktrace: " + Log.getStackTraceString(exception)); - return errorMap; + return errorList; } } diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebChromeClientFlutterApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebChromeClientFlutterApiImpl.java index cf263e2a6d34..92f0e41905cc 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebChromeClientFlutterApiImpl.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebChromeClientFlutterApiImpl.java @@ -4,10 +4,14 @@ package io.flutter.plugins.webviewflutter; +import android.os.Build; import android.webkit.WebChromeClient; import android.webkit.WebView; +import androidx.annotation.RequiresApi; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.WebChromeClientFlutterApi; +import java.util.List; +import java.util.Objects; /** * Flutter Api implementation for {@link WebChromeClient}. @@ -15,6 +19,7 @@ *

Passes arguments of callbacks methods from a {@link WebChromeClient} to Dart. */ public class WebChromeClientFlutterApiImpl extends WebChromeClientFlutterApi { + private final BinaryMessenger binaryMessenger; private final InstanceManager instanceManager; /** @@ -26,6 +31,7 @@ public class WebChromeClientFlutterApiImpl extends WebChromeClientFlutterApi { public WebChromeClientFlutterApiImpl( BinaryMessenger binaryMessenger, InstanceManager instanceManager) { super(binaryMessenger); + this.binaryMessenger = binaryMessenger; this.instanceManager = instanceManager; } @@ -40,6 +46,27 @@ public void onProgressChanged( getIdentifierForClient(webChromeClient), webViewIdentifier, progress, callback); } + /** Passes arguments from {@link WebChromeClient#onShowFileChooser} to Dart. */ + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public void onShowFileChooser( + WebChromeClient webChromeClient, + WebView webView, + WebChromeClient.FileChooserParams fileChooserParams, + Reply> callback) { + Long paramsInstanceId = instanceManager.getIdentifierForStrongReference(fileChooserParams); + if (paramsInstanceId == null) { + final FileChooserParamsFlutterApiImpl flutterApi = + new FileChooserParamsFlutterApiImpl(binaryMessenger, instanceManager); + paramsInstanceId = flutterApi.create(fileChooserParams, reply -> {}); + } + + onShowFileChooser( + Objects.requireNonNull(instanceManager.getIdentifierForStrongReference(webChromeClient)), + Objects.requireNonNull(instanceManager.getIdentifierForStrongReference(webView)), + paramsInstanceId, + callback); + } + private long getIdentifierForClient(WebChromeClient webChromeClient) { final Long identifier = instanceManager.getIdentifierForStrongReference(webChromeClient); if (identifier == null) { diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebChromeClientHostApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebChromeClientHostApiImpl.java index 3fa4a2f9c298..a5825c0133ec 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebChromeClientHostApiImpl.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebChromeClientHostApiImpl.java @@ -4,8 +4,10 @@ package io.flutter.plugins.webviewflutter; +import android.net.Uri; import android.os.Build; import android.os.Message; +import android.webkit.ValueCallback; import android.webkit.WebChromeClient; import android.webkit.WebResourceRequest; import android.webkit.WebView; @@ -15,6 +17,7 @@ import androidx.annotation.RequiresApi; import androidx.annotation.VisibleForTesting; import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.WebChromeClientHostApi; +import java.util.Objects; /** * Host api implementation for {@link WebChromeClient}. @@ -31,6 +34,7 @@ public class WebChromeClientHostApiImpl implements WebChromeClientHostApi { */ public static class WebChromeClientImpl extends SecureWebChromeClient { private final WebChromeClientFlutterApiImpl flutterApi; + private boolean returnValueForOnShowFileChooser = false; /** * Creates a {@link WebChromeClient} that passes arguments of callbacks methods to Dart. @@ -45,6 +49,36 @@ public WebChromeClientImpl(@NonNull WebChromeClientFlutterApiImpl flutterApi) { public void onProgressChanged(WebView view, int progress) { flutterApi.onProgressChanged(this, view, (long) progress, reply -> {}); } + + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + @Override + public boolean onShowFileChooser( + WebView webView, + ValueCallback filePathCallback, + FileChooserParams fileChooserParams) { + final boolean currentReturnValueForOnShowFileChooser = returnValueForOnShowFileChooser; + flutterApi.onShowFileChooser( + this, + webView, + fileChooserParams, + reply -> { + // The returned list of file paths can only be passed to `filePathCallback` if the + // `onShowFileChooser` method returned true. + if (currentReturnValueForOnShowFileChooser) { + final Uri[] filePaths = new Uri[reply.size()]; + for (int i = 0; i < reply.size(); i++) { + filePaths[i] = Uri.parse(reply.get(i)); + } + filePathCallback.onReceiveValue(filePaths); + } + }); + return currentReturnValueForOnShowFileChooser; + } + + /** Sets return value for {@link #onShowFileChooser}. */ + public void setReturnValueForOnShowFileChooser(boolean value) { + returnValueForOnShowFileChooser = value; + } } /** @@ -163,4 +197,12 @@ public void create(Long instanceId) { webChromeClientCreator.createWebChromeClient(flutterApi); instanceManager.addDartCreatedInstance(webChromeClient, instanceId); } + + @Override + public void setSynchronousReturnValueForOnShowFileChooser( + @NonNull Long instanceId, @NonNull Boolean value) { + final WebChromeClientImpl webChromeClient = + Objects.requireNonNull(instanceManager.getInstance(instanceId)); + webChromeClient.setReturnValueForOnShowFileChooser(value); + } } diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/FileChooserParamsTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/FileChooserParamsTest.java new file mode 100644 index 000000000000..3172ea4330c8 --- /dev/null +++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/FileChooserParamsTest.java @@ -0,0 +1,74 @@ +// Copyright 2013 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.webviewflutter; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.webkit.WebChromeClient.FileChooserParams; +import io.flutter.plugin.common.BinaryMessenger; +import java.util.Arrays; +import java.util.Objects; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +public class FileChooserParamsTest { + @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); + + @Mock public FileChooserParams mockFileChooserParams; + + @Mock public BinaryMessenger mockBinaryMessenger; + + InstanceManager instanceManager; + + @Before + public void setUp() { + instanceManager = InstanceManager.open(identifier -> {}); + } + + @After + public void tearDown() { + instanceManager.close(); + } + + @Test + public void flutterApiCreate() { + final FileChooserParamsFlutterApiImpl spyFlutterApi = + spy(new FileChooserParamsFlutterApiImpl(mockBinaryMessenger, instanceManager)); + + when(mockFileChooserParams.isCaptureEnabled()).thenReturn(true); + when(mockFileChooserParams.getAcceptTypes()).thenReturn(new String[] {"my", "list"}); + when(mockFileChooserParams.getMode()).thenReturn(FileChooserParams.MODE_OPEN_MULTIPLE); + when(mockFileChooserParams.getFilenameHint()).thenReturn("filenameHint"); + spyFlutterApi.create(mockFileChooserParams, reply -> {}); + + final long identifier = + Objects.requireNonNull( + instanceManager.getIdentifierForStrongReference(mockFileChooserParams)); + final ArgumentCaptor modeCaptor = + ArgumentCaptor.forClass(GeneratedAndroidWebView.FileChooserModeEnumData.class); + + verify(spyFlutterApi) + .create( + eq(identifier), + eq(true), + eq(Arrays.asList("my", "list")), + modeCaptor.capture(), + eq("filenameHint"), + any()); + assertEquals( + modeCaptor.getValue().getValue(), GeneratedAndroidWebView.FileChooserMode.OPEN_MULTIPLE); + } +} diff --git a/packages/webview_flutter/webview_flutter_android/example/integration_test/legacy/webview_flutter_test.dart b/packages/webview_flutter/webview_flutter_android/example/integration_test/legacy/webview_flutter_test.dart index 180175a22a0a..cbec6b767952 100644 --- a/packages/webview_flutter/webview_flutter_android/example/integration_test/legacy/webview_flutter_test.dart +++ b/packages/webview_flutter/webview_flutter_android/example/integration_test/legacy/webview_flutter_test.dart @@ -1557,7 +1557,7 @@ class CopyableObjectWithCallback with Copyable { class ClassWithCallbackClass { ClassWithCallbackClass() { callbackClass = CopyableObjectWithCallback( - withWeakRefenceTo( + withWeakReferenceTo( this, (WeakReference weakReference) { return () { diff --git a/packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test.dart b/packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test.dart index f6a55b1f2795..3f62053d0ac3 100644 --- a/packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test.dart +++ b/packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test.dart @@ -1181,7 +1181,7 @@ class CopyableObjectWithCallback with Copyable { class ClassWithCallbackClass { ClassWithCallbackClass() { callbackClass = CopyableObjectWithCallback( - withWeakRefenceTo( + withWeakReferenceTo( this, (WeakReference weakReference) { return () { diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_navigation_delegate.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_navigation_delegate.dart deleted file mode 100644 index 51c62764fde4..000000000000 --- a/packages/webview_flutter/webview_flutter_android/lib/src/android_navigation_delegate.dart +++ /dev/null @@ -1,318 +0,0 @@ -// Copyright 2013 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. - -import 'dart:async'; - -import 'package:flutter/foundation.dart'; -import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; - -import 'android_proxy.dart'; -import 'android_webview.dart' as android_webview; - -/// Signature for the `loadRequest` callback responsible for loading the [url] -/// after a navigation request has been approved. -typedef LoadRequestCallback = Future Function(LoadRequestParams params); - -/// Error returned in `WebView.onWebResourceError` when a web resource loading error has occurred. -@immutable -class AndroidWebResourceError extends WebResourceError { - /// Creates a new [AndroidWebResourceError]. - AndroidWebResourceError._({ - required super.errorCode, - required super.description, - super.isForMainFrame, - this.failingUrl, - }) : super( - errorType: _errorCodeToErrorType(errorCode), - ); - - /// Gets the URL for which the failing resource request was made. - final String? failingUrl; - - static WebResourceErrorType? _errorCodeToErrorType(int errorCode) { - switch (errorCode) { - case android_webview.WebViewClient.errorAuthentication: - return WebResourceErrorType.authentication; - case android_webview.WebViewClient.errorBadUrl: - return WebResourceErrorType.badUrl; - case android_webview.WebViewClient.errorConnect: - return WebResourceErrorType.connect; - case android_webview.WebViewClient.errorFailedSslHandshake: - return WebResourceErrorType.failedSslHandshake; - case android_webview.WebViewClient.errorFile: - return WebResourceErrorType.file; - case android_webview.WebViewClient.errorFileNotFound: - return WebResourceErrorType.fileNotFound; - case android_webview.WebViewClient.errorHostLookup: - return WebResourceErrorType.hostLookup; - case android_webview.WebViewClient.errorIO: - return WebResourceErrorType.io; - case android_webview.WebViewClient.errorProxyAuthentication: - return WebResourceErrorType.proxyAuthentication; - case android_webview.WebViewClient.errorRedirectLoop: - return WebResourceErrorType.redirectLoop; - case android_webview.WebViewClient.errorTimeout: - return WebResourceErrorType.timeout; - case android_webview.WebViewClient.errorTooManyRequests: - return WebResourceErrorType.tooManyRequests; - case android_webview.WebViewClient.errorUnknown: - return WebResourceErrorType.unknown; - case android_webview.WebViewClient.errorUnsafeResource: - return WebResourceErrorType.unsafeResource; - case android_webview.WebViewClient.errorUnsupportedAuthScheme: - return WebResourceErrorType.unsupportedAuthScheme; - case android_webview.WebViewClient.errorUnsupportedScheme: - return WebResourceErrorType.unsupportedScheme; - } - - throw ArgumentError( - 'Could not find a WebResourceErrorType for errorCode: $errorCode', - ); - } -} - -/// Object specifying creation parameters for creating a [AndroidNavigationDelegate]. -/// -/// When adding additional fields make sure they can be null or have a default -/// value to avoid breaking changes. See [PlatformNavigationDelegateCreationParams] for -/// more information. -@immutable -class AndroidNavigationDelegateCreationParams - extends PlatformNavigationDelegateCreationParams { - /// Creates a new [AndroidNavigationDelegateCreationParams] instance. - const AndroidNavigationDelegateCreationParams._({ - @visibleForTesting this.androidWebViewProxy = const AndroidWebViewProxy(), - }) : super(); - - /// Creates a [AndroidNavigationDelegateCreationParams] instance based on [PlatformNavigationDelegateCreationParams]. - factory AndroidNavigationDelegateCreationParams.fromPlatformNavigationDelegateCreationParams( - // Recommended placeholder to prevent being broken by platform interface. - // ignore: avoid_unused_constructor_parameters - PlatformNavigationDelegateCreationParams params, { - @visibleForTesting - AndroidWebViewProxy androidWebViewProxy = const AndroidWebViewProxy(), - }) { - return AndroidNavigationDelegateCreationParams._( - androidWebViewProxy: androidWebViewProxy, - ); - } - - /// Handles constructing objects and calling static methods for the Android WebView - /// native library. - @visibleForTesting - final AndroidWebViewProxy androidWebViewProxy; -} - -/// A place to register callback methods responsible to handle navigation events -/// triggered by the [android_webview.WebView]. -class AndroidNavigationDelegate extends PlatformNavigationDelegate { - /// Creates a new [AndroidNavigationkDelegate]. - AndroidNavigationDelegate(PlatformNavigationDelegateCreationParams params) - : super.implementation(params is AndroidNavigationDelegateCreationParams - ? params - : AndroidNavigationDelegateCreationParams - .fromPlatformNavigationDelegateCreationParams(params)) { - final WeakReference weakThis = - WeakReference(this); - - _webChromeClient = (this.params as AndroidNavigationDelegateCreationParams) - .androidWebViewProxy - .createAndroidWebChromeClient( - onProgressChanged: (android_webview.WebView webView, int progress) { - if (weakThis.target?._onProgress != null) { - weakThis.target!._onProgress!(progress); - } - }); - - _webViewClient = (this.params as AndroidNavigationDelegateCreationParams) - .androidWebViewProxy - .createAndroidWebViewClient( - onPageFinished: (android_webview.WebView webView, String url) { - if (weakThis.target?._onPageFinished != null) { - weakThis.target!._onPageFinished!(url); - } - }, - onPageStarted: (android_webview.WebView webView, String url) { - if (weakThis.target?._onPageStarted != null) { - weakThis.target!._onPageStarted!(url); - } - }, - onReceivedRequestError: ( - android_webview.WebView webView, - android_webview.WebResourceRequest request, - android_webview.WebResourceError error, - ) { - if (weakThis.target?._onWebResourceError != null) { - weakThis.target!._onWebResourceError!(AndroidWebResourceError._( - errorCode: error.errorCode, - description: error.description, - failingUrl: request.url, - isForMainFrame: request.isForMainFrame, - )); - } - }, - onReceivedError: ( - android_webview.WebView webView, - int errorCode, - String description, - String failingUrl, - ) { - if (weakThis.target?._onWebResourceError != null) { - weakThis.target!._onWebResourceError!(AndroidWebResourceError._( - errorCode: errorCode, - description: description, - failingUrl: failingUrl, - isForMainFrame: true, - )); - } - }, - requestLoading: ( - android_webview.WebView webView, - android_webview.WebResourceRequest request, - ) { - if (weakThis.target != null) { - weakThis.target!._handleNavigation( - request.url, - headers: request.requestHeaders, - isForMainFrame: request.isForMainFrame, - ); - } - }, - urlLoading: ( - android_webview.WebView webView, - String url, - ) { - if (weakThis.target != null) { - weakThis.target!._handleNavigation(url, isForMainFrame: true); - } - }, - ); - - _downloadListener = (this.params as AndroidNavigationDelegateCreationParams) - .androidWebViewProxy - .createDownloadListener( - onDownloadStart: ( - String url, - String userAgent, - String contentDisposition, - String mimetype, - int contentLength, - ) { - if (weakThis.target != null) { - weakThis.target?._handleNavigation(url, isForMainFrame: true); - } - }, - ); - } - - late final android_webview.WebChromeClient _webChromeClient; - - /// Gets the native [android_webview.WebChromeClient] that is bridged by this [AndroidNavigationDelegate]. - /// - /// Used by the [AndroidWebViewController] to set the `android_webview.WebView.setWebChromeClient`. - android_webview.WebChromeClient get androidWebChromeClient => - _webChromeClient; - - late final android_webview.WebViewClient _webViewClient; - - /// Gets the native [android_webview.WebViewClient] that is bridged by this [AndroidNavigationDelegate]. - /// - /// Used by the [AndroidWebViewController] to set the `android_webview.WebView.setWebViewClient`. - android_webview.WebViewClient get androidWebViewClient => _webViewClient; - - late final android_webview.DownloadListener _downloadListener; - - /// Gets the native [android_webview.DownloadListener] that is bridged by this [AndroidNavigationDelegate]. - /// - /// Used by the [AndroidWebViewController] to set the `android_webview.WebView.setDownloadListener`. - android_webview.DownloadListener get androidDownloadListener => - _downloadListener; - - PageEventCallback? _onPageFinished; - PageEventCallback? _onPageStarted; - ProgressCallback? _onProgress; - WebResourceErrorCallback? _onWebResourceError; - NavigationRequestCallback? _onNavigationRequest; - LoadRequestCallback? _onLoadRequest; - - void _handleNavigation( - String url, { - required bool isForMainFrame, - Map headers = const {}, - }) { - final LoadRequestCallback? onLoadRequest = _onLoadRequest; - final NavigationRequestCallback? onNavigationRequest = _onNavigationRequest; - - if (onNavigationRequest == null || onLoadRequest == null) { - return; - } - - final FutureOr returnValue = onNavigationRequest( - NavigationRequest( - url: url, - isMainFrame: isForMainFrame, - ), - ); - - if (returnValue is NavigationDecision && - returnValue == NavigationDecision.navigate) { - onLoadRequest(LoadRequestParams( - uri: Uri.parse(url), - headers: headers, - )); - } else if (returnValue is Future) { - returnValue.then((NavigationDecision shouldLoadUrl) { - if (shouldLoadUrl == NavigationDecision.navigate) { - onLoadRequest(LoadRequestParams( - uri: Uri.parse(url), - headers: headers, - )); - } - }); - } - } - - /// Invoked when loading the url after a navigation request is approved. - Future setOnLoadRequest( - LoadRequestCallback onLoadRequest, - ) async { - _onLoadRequest = onLoadRequest; - } - - @override - Future setOnNavigationRequest( - NavigationRequestCallback onNavigationRequest, - ) async { - _onNavigationRequest = onNavigationRequest; - _webViewClient.setSynchronousReturnValueForShouldOverrideUrlLoading(true); - } - - @override - Future setOnPageStarted( - PageEventCallback onPageStarted, - ) async { - _onPageStarted = onPageStarted; - } - - @override - Future setOnPageFinished( - PageEventCallback onPageFinished, - ) async { - _onPageFinished = onPageFinished; - } - - @override - Future setOnProgress( - ProgressCallback onProgress, - ) async { - _onProgress = onProgress; - } - - @override - Future setOnWebResourceError( - WebResourceErrorCallback onWebResourceError, - ) async { - _onWebResourceError = onWebResourceError; - } -} diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_proxy.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_proxy.dart index db247ee41d1c..9437e9dd3eb4 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/android_proxy.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_proxy.dart @@ -37,6 +37,11 @@ class AndroidWebViewProxy { final android_webview.WebChromeClient Function({ void Function(android_webview.WebView webView, int progress)? onProgressChanged, + Future> Function( + android_webview.WebView webView, + android_webview.FileChooserParams params, + )? + onShowFileChooser, }) createAndroidWebChromeClient; /// Constructs a [android_webview.WebViewClient]. diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart index 66f93dde1679..f7d536ce3972 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart @@ -20,6 +20,8 @@ import 'android_webview.pigeon.dart'; import 'android_webview_api_impls.dart'; import 'instance_manager.dart'; +export 'android_webview_api_impls.dart' show FileChooserMode; + /// Root of the Java class hierarchy. /// /// See https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html. @@ -871,7 +873,8 @@ class DownloadListener extends JavaObject { /// Handles JavaScript dialogs, favicons, titles, and the progress for [WebView]. class WebChromeClient extends JavaObject { /// Constructs a [WebChromeClient]. - WebChromeClient({this.onProgressChanged}) : super.detached() { + WebChromeClient({this.onProgressChanged, this.onShowFileChooser}) + : super.detached() { AndroidWebViewFlutterApis.instance.ensureSetUp(); api.createFromInstance(this); } @@ -881,7 +884,10 @@ class WebChromeClient extends JavaObject { /// /// This should only be used by subclasses created by this library or to /// create copies. - WebChromeClient.detached({this.onProgressChanged}) : super.detached(); + WebChromeClient.detached({ + this.onProgressChanged, + this.onShowFileChooser, + }) : super.detached(); /// Pigeon Host Api implementation for [WebChromeClient]. @visibleForTesting @@ -890,9 +896,95 @@ class WebChromeClient extends JavaObject { /// Notify the host application that a file should be downloaded. final void Function(WebView webView, int progress)? onProgressChanged; + /// Indicates the client should show a file chooser. + /// + /// To handle the request for a file chooser with this callback, passing true + /// to [setSynchronousReturnValueForOnShowFileChooser] is required. Otherwise, + /// the returned list of strings will be ignored and the client will use the + /// default handling of a file chooser request. + /// + /// Only invoked on Android versions 21+. + final Future> Function( + WebView webView, + FileChooserParams params, + )? onShowFileChooser; + + /// Sets the required synchronous return value for the Java method, + /// `WebChromeClient.onShowFileChooser(...)`. + /// + /// The Java method, `WebChromeClient.onShowFileChooser(...)`, requires + /// a boolean to be returned and this method sets the returned value for all + /// calls to the Java method. + /// + /// Setting this to true indicates that all file chooser requests should be + /// handled by [onShowFileChooser] and the returned list of Strings will be + /// returned to the WebView. Otherwise, the client will use the default + /// handling and the returned value in [onShowFileChooser] will be ignored. + /// + /// Requires [onShowFileChooser] to be nonnull. + /// + /// Defaults to false. + Future setSynchronousReturnValueForOnShowFileChooser( + bool value, + ) { + if (value && onShowFileChooser != null) { + throw StateError( + 'Setting this to true requires `onShowFileChooser` to be nonnull.', + ); + } + return api.setSynchronousReturnValueForOnShowFileChooserFromInstance( + this, + value, + ); + } + @override WebChromeClient copy() { - return WebChromeClient.detached(onProgressChanged: onProgressChanged); + return WebChromeClient.detached( + onProgressChanged: onProgressChanged, + onShowFileChooser: onShowFileChooser, + ); + } +} + +/// Parameters received when a [WebChromeClient] should show a file chooser. +/// +/// See https://developer.android.com/reference/android/webkit/WebChromeClient.FileChooserParams. +class FileChooserParams extends JavaObject { + /// Constructs a [FileChooserParams] without creating the associated Java + /// object. + /// + /// This should only be used by subclasses created by this library or to + /// create copies. + FileChooserParams.detached({ + required this.isCaptureEnabled, + required this.acceptTypes, + required this.filenameHint, + required this.mode, + super.binaryMessenger, + super.instanceManager, + }) : super.detached(); + + /// Preference for a live media captured value (e.g. Camera, Microphone). + final bool isCaptureEnabled; + + /// A list of acceptable MIME types. + final List acceptTypes; + + /// The file name of a default selection if specified, or null. + final String? filenameHint; + + /// Mode of how to select files for a file chooser. + final FileChooserMode mode; + + @override + FileChooserParams copy() { + return FileChooserParams.detached( + isCaptureEnabled: isCaptureEnabled, + acceptTypes: acceptTypes, + filenameHint: filenameHint, + mode: mode, + ); } } diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.pigeon.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.pigeon.dart index 5bdab16d6720..d3c306a10238 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.pigeon.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.pigeon.dart @@ -1,7 +1,7 @@ // Copyright 2013 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. -// Autogenerated from Pigeon (v4.2.3), do not edit directly. +// Autogenerated from Pigeon (v4.2.14), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import import 'dart:async'; @@ -10,6 +10,48 @@ import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; import 'package:flutter/services.dart'; +/// Mode of how to select files for a file chooser. +/// +/// See https://developer.android.com/reference/android/webkit/WebChromeClient.FileChooserParams. +enum FileChooserMode { + /// Open single file and requires that the file exists before allowing the + /// user to pick it. + /// + /// See https://developer.android.com/reference/android/webkit/WebChromeClient.FileChooserParams#MODE_OPEN. + open, + + /// Similar to [open] but allows multiple files to be selected. + /// + /// See https://developer.android.com/reference/android/webkit/WebChromeClient.FileChooserParams#MODE_OPEN_MULTIPLE. + openMultiple, + + /// Allows picking a nonexistent file and saving it. + /// + /// See https://developer.android.com/reference/android/webkit/WebChromeClient.FileChooserParams#MODE_SAVE. + save, +} + +class FileChooserModeEnumData { + FileChooserModeEnumData({ + required this.value, + }); + + FileChooserMode value; + + Object encode() { + return [ + value.index, + ]; + } + + static FileChooserModeEnumData decode(Object result) { + result as List; + return FileChooserModeEnumData( + value: FileChooserMode.values[result[0]! as int], + ); + } +} + class WebResourceRequestData { WebResourceRequestData({ required this.url, @@ -21,33 +63,38 @@ class WebResourceRequestData { }); String url; + bool isForMainFrame; + bool? isRedirect; + bool hasGesture; + String method; + Map requestHeaders; Object encode() { - final Map pigeonMap = {}; - pigeonMap['url'] = url; - pigeonMap['isForMainFrame'] = isForMainFrame; - pigeonMap['isRedirect'] = isRedirect; - pigeonMap['hasGesture'] = hasGesture; - pigeonMap['method'] = method; - pigeonMap['requestHeaders'] = requestHeaders; - return pigeonMap; - } - - static WebResourceRequestData decode(Object message) { - final Map pigeonMap = message as Map; + return [ + url, + isForMainFrame, + isRedirect, + hasGesture, + method, + requestHeaders, + ]; + } + + static WebResourceRequestData decode(Object result) { + result as List; return WebResourceRequestData( - url: pigeonMap['url']! as String, - isForMainFrame: pigeonMap['isForMainFrame']! as bool, - isRedirect: pigeonMap['isRedirect'] as bool?, - hasGesture: pigeonMap['hasGesture']! as bool, - method: pigeonMap['method']! as String, - requestHeaders: (pigeonMap['requestHeaders'] as Map?)! - .cast(), + url: result[0]! as String, + isForMainFrame: result[1]! as bool, + isRedirect: result[2] as bool?, + hasGesture: result[3]! as bool, + method: result[4]! as String, + requestHeaders: + (result[5] as Map?)!.cast(), ); } } @@ -59,20 +106,21 @@ class WebResourceErrorData { }); int errorCode; + String description; Object encode() { - final Map pigeonMap = {}; - pigeonMap['errorCode'] = errorCode; - pigeonMap['description'] = description; - return pigeonMap; + return [ + errorCode, + description, + ]; } - static WebResourceErrorData decode(Object message) { - final Map pigeonMap = message as Map; + static WebResourceErrorData decode(Object result) { + result as List; return WebResourceErrorData( - errorCode: pigeonMap['errorCode']! as int, - description: pigeonMap['description']! as String, + errorCode: result[0]! as int, + description: result[1]! as String, ); } } @@ -84,20 +132,21 @@ class WebViewPoint { }); int x; + int y; Object encode() { - final Map pigeonMap = {}; - pigeonMap['x'] = x; - pigeonMap['y'] = y; - return pigeonMap; + return [ + x, + y, + ]; } - static WebViewPoint decode(Object message) { - final Map pigeonMap = message as Map; + static WebViewPoint decode(Object result) { + result as List; return WebViewPoint( - x: pigeonMap['x']! as int, - y: pigeonMap['y']! as int, + x: result[0]! as int, + y: result[1]! as int, ); } } @@ -121,20 +170,18 @@ class JavaObjectHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.JavaObjectHostApi.dispose', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = - await channel.send([arg_identifier]) as Map?; - if (replyMap == null) { + final List? replyList = + await channel.send([arg_identifier]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -149,6 +196,7 @@ abstract class JavaObjectFlutterApi { static const MessageCodec codec = StandardMessageCodec(); void dispose(int identifier); + static void setup(JavaObjectFlutterApi? api, {BinaryMessenger? binaryMessenger}) { { @@ -187,28 +235,25 @@ class CookieManagerHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.CookieManagerHostApi.clearCookies', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = - await channel.send(null) as Map?; - if (replyMap == null) { + final List? replyList = await channel.send(null) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); - } else if (replyMap['result'] == null) { + } else if (replyList[0] == null) { throw PlatformException( code: 'null-error', message: 'Host platform returned null value for non-null return value.', ); } else { - return (replyMap['result'] as bool?)!; + return (replyList[0] as bool?)!; } } @@ -216,20 +261,18 @@ class CookieManagerHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.CookieManagerHostApi.setCookie', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = await channel - .send([arg_url, arg_value]) as Map?; - if (replyMap == null) { + final List? replyList = + await channel.send([arg_url, arg_value]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -275,21 +318,19 @@ class WebViewHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.create', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = + final List? replyList = await channel.send([arg_instanceId, arg_useHybridComposition]) - as Map?; - if (replyMap == null) { + as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -301,21 +342,19 @@ class WebViewHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.loadData', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = await channel.send( + final List? replyList = await channel.send( [arg_instanceId, arg_data, arg_mimeType, arg_encoding]) - as Map?; - if (replyMap == null) { + as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -332,26 +371,24 @@ class WebViewHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.loadDataWithBaseUrl', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = await channel.send([ + final List? replyList = await channel.send([ arg_instanceId, arg_baseUrl, arg_data, arg_mimeType, arg_encoding, arg_historyUrl - ]) as Map?; - if (replyMap == null) { + ]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -363,21 +400,19 @@ class WebViewHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.loadUrl', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = + final List? replyList = await channel.send([arg_instanceId, arg_url, arg_headers]) - as Map?; - if (replyMap == null) { + as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -389,21 +424,18 @@ class WebViewHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.postUrl', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = - await channel.send([arg_instanceId, arg_url, arg_data]) - as Map?; - if (replyMap == null) { + final List? replyList = await channel + .send([arg_instanceId, arg_url, arg_data]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -414,23 +446,21 @@ class WebViewHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.getUrl', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = - await channel.send([arg_instanceId]) as Map?; - if (replyMap == null) { + final List? replyList = + await channel.send([arg_instanceId]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { - return (replyMap['result'] as String?); + return (replyList[0] as String?); } } @@ -438,28 +468,26 @@ class WebViewHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.canGoBack', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = - await channel.send([arg_instanceId]) as Map?; - if (replyMap == null) { + final List? replyList = + await channel.send([arg_instanceId]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); - } else if (replyMap['result'] == null) { + } else if (replyList[0] == null) { throw PlatformException( code: 'null-error', message: 'Host platform returned null value for non-null return value.', ); } else { - return (replyMap['result'] as bool?)!; + return (replyList[0] as bool?)!; } } @@ -467,28 +495,26 @@ class WebViewHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.canGoForward', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = - await channel.send([arg_instanceId]) as Map?; - if (replyMap == null) { + final List? replyList = + await channel.send([arg_instanceId]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); - } else if (replyMap['result'] == null) { + } else if (replyList[0] == null) { throw PlatformException( code: 'null-error', message: 'Host platform returned null value for non-null return value.', ); } else { - return (replyMap['result'] as bool?)!; + return (replyList[0] as bool?)!; } } @@ -496,20 +522,18 @@ class WebViewHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.goBack', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = - await channel.send([arg_instanceId]) as Map?; - if (replyMap == null) { + final List? replyList = + await channel.send([arg_instanceId]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -520,20 +544,18 @@ class WebViewHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.goForward', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = - await channel.send([arg_instanceId]) as Map?; - if (replyMap == null) { + final List? replyList = + await channel.send([arg_instanceId]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -544,20 +566,18 @@ class WebViewHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.reload', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = - await channel.send([arg_instanceId]) as Map?; - if (replyMap == null) { + final List? replyList = + await channel.send([arg_instanceId]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -568,21 +588,19 @@ class WebViewHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.clearCache', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = + final List? replyList = await channel.send([arg_instanceId, arg_includeDiskFiles]) - as Map?; - if (replyMap == null) { + as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -594,24 +612,22 @@ class WebViewHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.evaluateJavascript', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = + final List? replyList = await channel.send([arg_instanceId, arg_javascriptString]) - as Map?; - if (replyMap == null) { + as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { - return (replyMap['result'] as String?); + return (replyList[0] as String?); } } @@ -619,23 +635,21 @@ class WebViewHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.getTitle', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = - await channel.send([arg_instanceId]) as Map?; - if (replyMap == null) { + final List? replyList = + await channel.send([arg_instanceId]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { - return (replyMap['result'] as String?); + return (replyList[0] as String?); } } @@ -643,21 +657,18 @@ class WebViewHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.scrollTo', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = - await channel.send([arg_instanceId, arg_x, arg_y]) - as Map?; - if (replyMap == null) { + final List? replyList = await channel + .send([arg_instanceId, arg_x, arg_y]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -668,21 +679,18 @@ class WebViewHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.scrollBy', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = - await channel.send([arg_instanceId, arg_x, arg_y]) - as Map?; - if (replyMap == null) { + final List? replyList = await channel + .send([arg_instanceId, arg_x, arg_y]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -693,28 +701,26 @@ class WebViewHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.getScrollX', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = - await channel.send([arg_instanceId]) as Map?; - if (replyMap == null) { + final List? replyList = + await channel.send([arg_instanceId]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); - } else if (replyMap['result'] == null) { + } else if (replyList[0] == null) { throw PlatformException( code: 'null-error', message: 'Host platform returned null value for non-null return value.', ); } else { - return (replyMap['result'] as int?)!; + return (replyList[0] as int?)!; } } @@ -722,28 +728,26 @@ class WebViewHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.getScrollY', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = - await channel.send([arg_instanceId]) as Map?; - if (replyMap == null) { + final List? replyList = + await channel.send([arg_instanceId]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); - } else if (replyMap['result'] == null) { + } else if (replyList[0] == null) { throw PlatformException( code: 'null-error', message: 'Host platform returned null value for non-null return value.', ); } else { - return (replyMap['result'] as int?)!; + return (replyList[0] as int?)!; } } @@ -751,28 +755,26 @@ class WebViewHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.getScrollPosition', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = - await channel.send([arg_instanceId]) as Map?; - if (replyMap == null) { + final List? replyList = + await channel.send([arg_instanceId]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); - } else if (replyMap['result'] == null) { + } else if (replyList[0] == null) { throw PlatformException( code: 'null-error', message: 'Host platform returned null value for non-null return value.', ); } else { - return (replyMap['result'] as WebViewPoint?)!; + return (replyList[0] as WebViewPoint?)!; } } @@ -781,20 +783,18 @@ class WebViewHostApi { 'dev.flutter.pigeon.WebViewHostApi.setWebContentsDebuggingEnabled', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = - await channel.send([arg_enabled]) as Map?; - if (replyMap == null) { + final List? replyList = + await channel.send([arg_enabled]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -806,21 +806,19 @@ class WebViewHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.setWebViewClient', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = await channel + final List? replyList = await channel .send([arg_instanceId, arg_webViewClientInstanceId]) - as Map?; - if (replyMap == null) { + as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -832,21 +830,19 @@ class WebViewHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.addJavaScriptChannel', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = await channel + final List? replyList = await channel .send([arg_instanceId, arg_javaScriptChannelInstanceId]) - as Map?; - if (replyMap == null) { + as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -858,21 +854,19 @@ class WebViewHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.removeJavaScriptChannel', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = await channel + final List? replyList = await channel .send([arg_instanceId, arg_javaScriptChannelInstanceId]) - as Map?; - if (replyMap == null) { + as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -884,21 +878,19 @@ class WebViewHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.setDownloadListener', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = + final List? replyList = await channel.send([arg_instanceId, arg_listenerInstanceId]) - as Map?; - if (replyMap == null) { + as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -910,21 +902,19 @@ class WebViewHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.setWebChromeClient', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = + final List? replyList = await channel.send([arg_instanceId, arg_clientInstanceId]) - as Map?; - if (replyMap == null) { + as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -935,20 +925,18 @@ class WebViewHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.setBackgroundColor', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = await channel - .send([arg_instanceId, arg_color]) as Map?; - if (replyMap == null) { + final List? replyList = await channel + .send([arg_instanceId, arg_color]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -970,21 +958,19 @@ class WebSettingsHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebSettingsHostApi.create', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = + final List? replyList = await channel.send([arg_instanceId, arg_webViewInstanceId]) - as Map?; - if (replyMap == null) { + as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -995,20 +981,18 @@ class WebSettingsHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebSettingsHostApi.setDomStorageEnabled', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = await channel - .send([arg_instanceId, arg_flag]) as Map?; - if (replyMap == null) { + final List? replyList = await channel + .send([arg_instanceId, arg_flag]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -1021,20 +1005,18 @@ class WebSettingsHostApi { 'dev.flutter.pigeon.WebSettingsHostApi.setJavaScriptCanOpenWindowsAutomatically', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = await channel - .send([arg_instanceId, arg_flag]) as Map?; - if (replyMap == null) { + final List? replyList = await channel + .send([arg_instanceId, arg_flag]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -1047,20 +1029,18 @@ class WebSettingsHostApi { 'dev.flutter.pigeon.WebSettingsHostApi.setSupportMultipleWindows', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = await channel - .send([arg_instanceId, arg_support]) as Map?; - if (replyMap == null) { + final List? replyList = await channel + .send([arg_instanceId, arg_support]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -1071,20 +1051,18 @@ class WebSettingsHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebSettingsHostApi.setJavaScriptEnabled', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = await channel - .send([arg_instanceId, arg_flag]) as Map?; - if (replyMap == null) { + final List? replyList = await channel + .send([arg_instanceId, arg_flag]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -1096,21 +1074,18 @@ class WebSettingsHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebSettingsHostApi.setUserAgentString', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = - await channel.send([arg_instanceId, arg_userAgentString]) - as Map?; - if (replyMap == null) { + final List? replyList = await channel + .send([arg_instanceId, arg_userAgentString]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -1123,20 +1098,18 @@ class WebSettingsHostApi { 'dev.flutter.pigeon.WebSettingsHostApi.setMediaPlaybackRequiresUserGesture', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = await channel - .send([arg_instanceId, arg_require]) as Map?; - if (replyMap == null) { + final List? replyList = await channel + .send([arg_instanceId, arg_require]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -1147,20 +1120,18 @@ class WebSettingsHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebSettingsHostApi.setSupportZoom', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = await channel - .send([arg_instanceId, arg_support]) as Map?; - if (replyMap == null) { + final List? replyList = await channel + .send([arg_instanceId, arg_support]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -1172,21 +1143,18 @@ class WebSettingsHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebSettingsHostApi.setLoadWithOverviewMode', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = - await channel.send([arg_instanceId, arg_overview]) - as Map?; - if (replyMap == null) { + final List? replyList = await channel + .send([arg_instanceId, arg_overview]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -1197,20 +1165,18 @@ class WebSettingsHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebSettingsHostApi.setUseWideViewPort', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = await channel - .send([arg_instanceId, arg_use]) as Map?; - if (replyMap == null) { + final List? replyList = await channel + .send([arg_instanceId, arg_use]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -1222,20 +1188,18 @@ class WebSettingsHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebSettingsHostApi.setDisplayZoomControls', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = await channel - .send([arg_instanceId, arg_enabled]) as Map?; - if (replyMap == null) { + final List? replyList = await channel + .send([arg_instanceId, arg_enabled]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -1247,20 +1211,18 @@ class WebSettingsHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebSettingsHostApi.setBuiltInZoomControls', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = await channel - .send([arg_instanceId, arg_enabled]) as Map?; - if (replyMap == null) { + final List? replyList = await channel + .send([arg_instanceId, arg_enabled]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -1271,20 +1233,18 @@ class WebSettingsHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebSettingsHostApi.setAllowFileAccess', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = await channel - .send([arg_instanceId, arg_enabled]) as Map?; - if (replyMap == null) { + final List? replyList = await channel + .send([arg_instanceId, arg_enabled]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -1306,21 +1266,18 @@ class JavaScriptChannelHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.JavaScriptChannelHostApi.create', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = - await channel.send([arg_instanceId, arg_channelName]) - as Map?; - if (replyMap == null) { + final List? replyList = await channel + .send([arg_instanceId, arg_channelName]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -1332,6 +1289,7 @@ abstract class JavaScriptChannelFlutterApi { static const MessageCodec codec = StandardMessageCodec(); void postMessage(int instanceId, String message); + static void setup(JavaScriptChannelFlutterApi? api, {BinaryMessenger? binaryMessenger}) { { @@ -1373,20 +1331,18 @@ class WebViewClientHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewClientHostApi.create', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = - await channel.send([arg_instanceId]) as Map?; - if (replyMap == null) { + final List? replyList = + await channel.send([arg_instanceId]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -1399,20 +1355,18 @@ class WebViewClientHostApi { 'dev.flutter.pigeon.WebViewClientHostApi.setSynchronousReturnValueForShouldOverrideUrlLoading', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = await channel - .send([arg_instanceId, arg_value]) as Map?; - if (replyMap == null) { + final List? replyList = await channel + .send([arg_instanceId, arg_value]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -1454,14 +1408,20 @@ abstract class WebViewClientFlutterApi { static const MessageCodec codec = _WebViewClientFlutterApiCodec(); void onPageStarted(int instanceId, int webViewInstanceId, String url); + void onPageFinished(int instanceId, int webViewInstanceId, String url); + void onReceivedRequestError(int instanceId, int webViewInstanceId, WebResourceRequestData request, WebResourceErrorData error); + void onReceivedError(int instanceId, int webViewInstanceId, int errorCode, String description, String failingUrl); + void requestLoading( int instanceId, int webViewInstanceId, WebResourceRequestData request); + void urlLoading(int instanceId, int webViewInstanceId, String url); + static void setup(WebViewClientFlutterApi? api, {BinaryMessenger? binaryMessenger}) { { @@ -1647,20 +1607,18 @@ class DownloadListenerHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.DownloadListenerHostApi.create', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = - await channel.send([arg_instanceId]) as Map?; - if (replyMap == null) { + final List? replyList = + await channel.send([arg_instanceId]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -1673,6 +1631,7 @@ abstract class DownloadListenerFlutterApi { void onDownloadStart(int instanceId, String url, String userAgent, String contentDisposition, String mimetype, int contentLength); + static void setup(DownloadListenerFlutterApi? api, {BinaryMessenger? binaryMessenger}) { { @@ -1728,20 +1687,42 @@ class WebChromeClientHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebChromeClientHostApi.create', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = - await channel.send([arg_instanceId]) as Map?; - if (replyMap == null) { + final List? replyList = + await channel.send([arg_instanceId]) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else { + return; + } + } + + Future setSynchronousReturnValueForOnShowFileChooser( + int arg_instanceId, bool arg_value) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.WebChromeClientHostApi.setSynchronousReturnValueForOnShowFileChooser', + codec, + binaryMessenger: _binaryMessenger); + final List? replyList = await channel + .send([arg_instanceId, arg_value]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -1763,28 +1744,26 @@ class FlutterAssetManagerHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.FlutterAssetManagerHostApi.list', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = - await channel.send([arg_path]) as Map?; - if (replyMap == null) { + final List? replyList = + await channel.send([arg_path]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); - } else if (replyMap['result'] == null) { + } else if (replyList[0] == null) { throw PlatformException( code: 'null-error', message: 'Host platform returned null value for non-null return value.', ); } else { - return (replyMap['result'] as List?)!.cast(); + return (replyList[0] as List?)!.cast(); } } @@ -1793,28 +1772,26 @@ class FlutterAssetManagerHostApi { 'dev.flutter.pigeon.FlutterAssetManagerHostApi.getAssetFilePathByName', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = - await channel.send([arg_name]) as Map?; - if (replyMap == null) { + final List? replyList = + await channel.send([arg_name]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); - } else if (replyMap['result'] == null) { + } else if (replyList[0] == null) { throw PlatformException( code: 'null-error', message: 'Host platform returned null value for non-null return value.', ); } else { - return (replyMap['result'] as String?)!; + return (replyList[0] as String?)!; } } } @@ -1823,6 +1800,10 @@ abstract class WebChromeClientFlutterApi { static const MessageCodec codec = StandardMessageCodec(); void onProgressChanged(int instanceId, int webViewInstanceId, int progress); + + Future> onShowFileChooser( + int instanceId, int webViewInstanceId, int paramsInstanceId); + static void setup(WebChromeClientFlutterApi? api, {BinaryMessenger? binaryMessenger}) { { @@ -1852,6 +1833,33 @@ abstract class WebChromeClientFlutterApi { }); } } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.WebChromeClientFlutterApi.onShowFileChooser', + codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMessageHandler(null); + } else { + channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.WebChromeClientFlutterApi.onShowFileChooser was null.'); + final List args = (message as List?)!; + final int? arg_instanceId = (args[0] as int?); + assert(arg_instanceId != null, + 'Argument for dev.flutter.pigeon.WebChromeClientFlutterApi.onShowFileChooser was null, expected non-null int.'); + final int? arg_webViewInstanceId = (args[1] as int?); + assert(arg_webViewInstanceId != null, + 'Argument for dev.flutter.pigeon.WebChromeClientFlutterApi.onShowFileChooser was null, expected non-null int.'); + final int? arg_paramsInstanceId = (args[2] as int?); + assert(arg_paramsInstanceId != null, + 'Argument for dev.flutter.pigeon.WebChromeClientFlutterApi.onShowFileChooser was null, expected non-null int.'); + final List output = await api.onShowFileChooser( + arg_instanceId!, arg_webViewInstanceId!, arg_paramsInstanceId!); + return output; + }); + } + } } } @@ -1869,20 +1877,18 @@ class WebStorageHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebStorageHostApi.create', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = - await channel.send([arg_instanceId]) as Map?; - if (replyMap == null) { + final List? replyList = + await channel.send([arg_instanceId]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; @@ -1893,23 +1899,92 @@ class WebStorageHostApi { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebStorageHostApi.deleteAllData', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = - await channel.send([arg_instanceId]) as Map?; - if (replyMap == null) { + final List? replyList = + await channel.send([arg_instanceId]) as List?; + if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); - } else if (replyMap['error'] != null) { - final Map error = - (replyMap['error'] as Map?)!; + } else if (replyList.length > 1) { throw PlatformException( - code: (error['code'] as String?)!, - message: error['message'] as String?, - details: error['details'], + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], ); } else { return; } } } + +class _FileChooserParamsFlutterApiCodec extends StandardMessageCodec { + const _FileChooserParamsFlutterApiCodec(); + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is FileChooserModeEnumData) { + buffer.putUint8(128); + writeValue(buffer, value.encode()); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + case 128: + return FileChooserModeEnumData.decode(readValue(buffer)!); + + default: + return super.readValueOfType(type, buffer); + } + } +} + +/// Handles callbacks methods for the native Java FileChooserParams class. +/// +/// See https://developer.android.com/reference/android/webkit/WebChromeClient.FileChooserParams. +abstract class FileChooserParamsFlutterApi { + static const MessageCodec codec = + _FileChooserParamsFlutterApiCodec(); + + void create(int instanceId, bool isCaptureEnabled, List acceptTypes, + FileChooserModeEnumData mode, String? filenameHint); + + static void setup(FileChooserParamsFlutterApi? api, + {BinaryMessenger? binaryMessenger}) { + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.FileChooserParamsFlutterApi.create', codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMessageHandler(null); + } else { + channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.FileChooserParamsFlutterApi.create was null.'); + final List args = (message as List?)!; + final int? arg_instanceId = (args[0] as int?); + assert(arg_instanceId != null, + 'Argument for dev.flutter.pigeon.FileChooserParamsFlutterApi.create was null, expected non-null int.'); + final bool? arg_isCaptureEnabled = (args[1] as bool?); + assert(arg_isCaptureEnabled != null, + 'Argument for dev.flutter.pigeon.FileChooserParamsFlutterApi.create was null, expected non-null bool.'); + final List? arg_acceptTypes = + (args[2] as List?)?.cast(); + assert(arg_acceptTypes != null, + 'Argument for dev.flutter.pigeon.FileChooserParamsFlutterApi.create was null, expected non-null List.'); + final FileChooserModeEnumData? arg_mode = + (args[3] as FileChooserModeEnumData?); + assert(arg_mode != null, + 'Argument for dev.flutter.pigeon.FileChooserParamsFlutterApi.create was null, expected non-null FileChooserModeEnumData.'); + final String? arg_filenameHint = (args[4] as String?); + api.create(arg_instanceId!, arg_isCaptureEnabled!, arg_acceptTypes!, + arg_mode!, arg_filenameHint); + return; + }); + } + } + } +} diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_api_impls.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_api_impls.dart index 8aa5f7d9dab4..023f3c4ac421 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_api_impls.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_api_impls.dart @@ -13,6 +13,8 @@ import 'android_webview.dart'; import 'android_webview.pigeon.dart'; import 'instance_manager.dart'; +export 'android_webview.pigeon.dart' show FileChooserMode; + /// Converts [WebResourceRequestData] to [WebResourceRequest] WebResourceRequest _toWebResourceRequest(WebResourceRequestData data) { return WebResourceRequest( @@ -42,6 +44,7 @@ class AndroidWebViewFlutterApis { WebViewClientFlutterApiImpl? webViewClientFlutterApi, WebChromeClientFlutterApiImpl? webChromeClientFlutterApi, JavaScriptChannelFlutterApiImpl? javaScriptChannelFlutterApi, + FileChooserParamsFlutterApiImpl? fileChooserParamsFlutterApi, }) { this.javaObjectFlutterApi = javaObjectFlutterApi ?? JavaObjectFlutterApiImpl(); @@ -53,6 +56,8 @@ class AndroidWebViewFlutterApis { webChromeClientFlutterApi ?? WebChromeClientFlutterApiImpl(); this.javaScriptChannelFlutterApi = javaScriptChannelFlutterApi ?? JavaScriptChannelFlutterApiImpl(); + this.fileChooserParamsFlutterApi = + fileChooserParamsFlutterApi ?? FileChooserParamsFlutterApiImpl(); } static bool _haveBeenSetUp = false; @@ -77,6 +82,9 @@ class AndroidWebViewFlutterApis { /// Flutter Api for [JavaScriptChannel]. late final JavaScriptChannelFlutterApiImpl javaScriptChannelFlutterApi; + /// Flutter Api for [FileChooserParams]. + late final FileChooserParamsFlutterApiImpl fileChooserParamsFlutterApi; + /// Ensures all the Flutter APIs have been setup to receive calls from native code. void ensureSetUp() { if (!_haveBeenSetUp) { @@ -85,6 +93,7 @@ class AndroidWebViewFlutterApis { WebViewClientFlutterApi.setup(webViewClientFlutterApi); WebChromeClientFlutterApi.setup(webChromeClientFlutterApi); JavaScriptChannelFlutterApi.setup(javaScriptChannelFlutterApi); + FileChooserParamsFlutterApi.setup(fileChooserParamsFlutterApi); _haveBeenSetUp = true; } } @@ -781,6 +790,17 @@ class WebChromeClientHostApiImpl extends WebChromeClientHostApi { return create(identifier); } } + + /// Helper method to convert instances ids to objects. + Future setSynchronousReturnValueForOnShowFileChooserFromInstance( + WebChromeClient instance, + bool value, + ) { + return setSynchronousReturnValueForOnShowFileChooser( + instanceManager.getIdentifier(instance)!, + value, + ); + } } /// Flutter api implementation for [DownloadListener]. @@ -810,6 +830,26 @@ class WebChromeClientFlutterApiImpl extends WebChromeClientFlutterApi { instance.onProgressChanged!(webViewInstance!, progress); } } + + @override + Future> onShowFileChooser( + int instanceId, + int webViewInstanceId, + int paramsInstanceId, + ) { + final WebChromeClient instance = + instanceManager.getInstanceWithWeakReference(instanceId)!; + if (instance.onShowFileChooser != null) { + return instance.onShowFileChooser!( + instanceManager.getInstanceWithWeakReference(webViewInstanceId)! + as WebView, + instanceManager.getInstanceWithWeakReference(paramsInstanceId)! + as FileChooserParams, + ); + } + + return Future>.value(const []); + } } /// Host api implementation for [WebStorage]. @@ -836,3 +876,32 @@ class WebStorageHostApiImpl extends WebStorageHostApi { return deleteAllData(instanceManager.getIdentifier(instance)!); } } + +/// Flutter api implementation for [FileChooserParams]. +class FileChooserParamsFlutterApiImpl extends FileChooserParamsFlutterApi { + /// Constructs a [FileChooserParamsFlutterApiImpl]. + FileChooserParamsFlutterApiImpl({InstanceManager? instanceManager}) + : instanceManager = instanceManager ?? JavaObject.globalInstanceManager; + + /// Maintains instances stored to communicate with java objects. + final InstanceManager instanceManager; + + @override + void create( + int instanceId, + bool isCaptureEnabled, + List acceptTypes, + FileChooserModeEnumData mode, + String? filenameHint, + ) { + instanceManager.addHostCreatedInstance( + FileChooserParams.detached( + isCaptureEnabled: isCaptureEnabled, + acceptTypes: acceptTypes.cast(), + mode: mode.value, + filenameHint: filenameHint, + ), + instanceId, + ); + } +} diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart index 2e32705a83ea..f6e54b86d8a0 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart @@ -2,6 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#104231) +// ignore: unnecessary_import +import 'dart:async'; + // TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#104231) // ignore: unnecessary_import import 'dart:typed_data'; @@ -11,10 +15,8 @@ import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; -import 'android_navigation_delegate.dart'; import 'android_proxy.dart'; import 'android_webview.dart' as android_webview; -import 'android_webview.dart'; import 'instance_manager.dart'; import 'platform_views_service_proxy.dart'; import 'weak_reference_utils.dart'; @@ -76,6 +78,8 @@ class AndroidWebViewController extends PlatformWebViewController { _webView.settings.setUseWideViewPort(true); _webView.settings.setDisplayZoomControls(false); _webView.settings.setBuiltInZoomControls(true); + + _webView.setWebChromeClient(_webChromeClient); } AndroidWebViewControllerCreationParams get _androidWebViewParams => @@ -91,6 +95,30 @@ class AndroidWebViewController extends PlatformWebViewController { useHybridComposition: true, ); + late final android_webview.WebChromeClient _webChromeClient = + withWeakReferenceTo(this, + (WeakReference weakReference) { + return _androidWebViewParams.androidWebViewProxy + .createAndroidWebChromeClient( + onProgressChanged: (android_webview.WebView webView, int progress) { + if (weakReference.target?._currentNavigationDelegate._onProgress != + null) { + weakReference + .target!._currentNavigationDelegate._onProgress!(progress); + } + }, + onShowFileChooser: (android_webview.WebView webView, + android_webview.FileChooserParams params) async { + if (weakReference.target?._onShowFileSelectorCallback != null) { + return weakReference.target!._onShowFileSelectorCallback!( + FileSelectorParams._fromFileChooserParams(params), + ); + } + return []; + }, + ); + }); + /// The native [android_webview.FlutterAssetManager] allows managing assets. late final android_webview.FlutterAssetManager _flutterAssetManager = _androidWebViewParams.androidWebViewProxy.createFlutterAssetManager(); @@ -103,6 +131,9 @@ class AndroidWebViewController extends PlatformWebViewController { // ignore: unused_field late AndroidNavigationDelegate _currentNavigationDelegate; + Future> Function(FileSelectorParams)? + _onShowFileSelectorCallback; + /// Whether to enable the platform's webview content debugging tools. /// /// Defaults to false. @@ -213,7 +244,6 @@ class AndroidWebViewController extends PlatformWebViewController { _currentNavigationDelegate = handler; handler.setOnLoadRequest(loadRequest); _webView.setWebViewClient(handler.androidWebViewClient); - _webView.setWebChromeClient(handler.androidWebChromeClient); _webView.setDownloadListener(handler.androidDownloadListener); } @@ -309,6 +339,79 @@ class AndroidWebViewController extends PlatformWebViewController { Future setMediaPlaybackRequiresUserGesture(bool require) { return _webView.settings.setMediaPlaybackRequiresUserGesture(require); } + + /// Sets the callback that is invoked when the client should show a file + /// selector. + Future setOnShowFileSelector( + Future> Function(FileSelectorParams params)? + onShowFileSelector, + ) { + _onShowFileSelectorCallback = onShowFileSelector; + return _webChromeClient.setSynchronousReturnValueForOnShowFileChooser( + onShowFileSelector != null, + ); + } +} + +/// Mode of how to select files for a file chooser. +enum FileSelectorMode { + /// Open single file and requires that the file exists before allowing the + /// user to pick it. + open, + + /// Similar to [open] but allows multiple files to be selected. + openMultiple, + + /// Allows picking a nonexistent file and saving it. + save, +} + +/// Parameters received when the `WebView` should show a file selector. +@immutable +class FileSelectorParams { + /// Constructs a [FileSelectorParams]. + const FileSelectorParams({ + required this.isCaptureEnabled, + required this.acceptTypes, + this.filenameHint, + required this.mode, + }); + + factory FileSelectorParams._fromFileChooserParams( + android_webview.FileChooserParams params, + ) { + final FileSelectorMode mode; + switch (params.mode) { + case android_webview.FileChooserMode.open: + mode = FileSelectorMode.open; + break; + case android_webview.FileChooserMode.openMultiple: + mode = FileSelectorMode.openMultiple; + break; + case android_webview.FileChooserMode.save: + mode = FileSelectorMode.save; + break; + } + + return FileSelectorParams( + isCaptureEnabled: params.isCaptureEnabled, + acceptTypes: params.acceptTypes, + mode: mode, + filenameHint: params.filenameHint, + ); + } + + /// Preference for a live media captured value (e.g. Camera, Microphone). + final bool isCaptureEnabled; + + /// A list of acceptable MIME types. + final List acceptTypes; + + /// The file name of a default selection if specified, or null. + final String? filenameHint; + + /// Mode of how to select files for a file selector. + final FileSelectorMode mode; } /// An implementation of [JavaScriptChannelParams] with the Android WebView API. @@ -325,7 +428,7 @@ class AndroidJavaScriptChannelParams extends JavaScriptChannelParams { }) : assert(name.isNotEmpty), _javaScriptChannel = webViewProxy.createJavaScriptChannel( name, - postMessage: withWeakRefenceTo( + postMessage: withWeakReferenceTo( onMessageReceived, (WeakReference weakReference) { return ( @@ -374,7 +477,8 @@ class AndroidWebViewWidgetCreationParams @visibleForTesting InstanceManager? instanceManager, @visibleForTesting this.platformViewsServiceProxy = const PlatformViewsServiceProxy(), - }) : instanceManager = instanceManager ?? JavaObject.globalInstanceManager; + }) : instanceManager = + instanceManager ?? android_webview.JavaObject.globalInstanceManager; /// Constructs a [WebKitWebViewWidgetCreationParams] using a /// [PlatformWebViewWidgetCreationParams]. @@ -488,3 +592,308 @@ class AndroidWebViewWidget extends PlatformWebViewWidget { } } } + +/// Signature for the `loadRequest` callback responsible for loading the [url] +/// after a navigation request has been approved. +typedef LoadRequestCallback = Future Function(LoadRequestParams params); + +/// Error returned in `WebView.onWebResourceError` when a web resource loading error has occurred. +@immutable +class AndroidWebResourceError extends WebResourceError { + /// Creates a new [AndroidWebResourceError]. + AndroidWebResourceError._({ + required super.errorCode, + required super.description, + super.isForMainFrame, + this.failingUrl, + }) : super( + errorType: _errorCodeToErrorType(errorCode), + ); + + /// Gets the URL for which the failing resource request was made. + final String? failingUrl; + + static WebResourceErrorType? _errorCodeToErrorType(int errorCode) { + switch (errorCode) { + case android_webview.WebViewClient.errorAuthentication: + return WebResourceErrorType.authentication; + case android_webview.WebViewClient.errorBadUrl: + return WebResourceErrorType.badUrl; + case android_webview.WebViewClient.errorConnect: + return WebResourceErrorType.connect; + case android_webview.WebViewClient.errorFailedSslHandshake: + return WebResourceErrorType.failedSslHandshake; + case android_webview.WebViewClient.errorFile: + return WebResourceErrorType.file; + case android_webview.WebViewClient.errorFileNotFound: + return WebResourceErrorType.fileNotFound; + case android_webview.WebViewClient.errorHostLookup: + return WebResourceErrorType.hostLookup; + case android_webview.WebViewClient.errorIO: + return WebResourceErrorType.io; + case android_webview.WebViewClient.errorProxyAuthentication: + return WebResourceErrorType.proxyAuthentication; + case android_webview.WebViewClient.errorRedirectLoop: + return WebResourceErrorType.redirectLoop; + case android_webview.WebViewClient.errorTimeout: + return WebResourceErrorType.timeout; + case android_webview.WebViewClient.errorTooManyRequests: + return WebResourceErrorType.tooManyRequests; + case android_webview.WebViewClient.errorUnknown: + return WebResourceErrorType.unknown; + case android_webview.WebViewClient.errorUnsafeResource: + return WebResourceErrorType.unsafeResource; + case android_webview.WebViewClient.errorUnsupportedAuthScheme: + return WebResourceErrorType.unsupportedAuthScheme; + case android_webview.WebViewClient.errorUnsupportedScheme: + return WebResourceErrorType.unsupportedScheme; + } + + throw ArgumentError( + 'Could not find a WebResourceErrorType for errorCode: $errorCode', + ); + } +} + +/// Object specifying creation parameters for creating a [AndroidNavigationDelegate]. +/// +/// When adding additional fields make sure they can be null or have a default +/// value to avoid breaking changes. See [PlatformNavigationDelegateCreationParams] for +/// more information. +@immutable +class AndroidNavigationDelegateCreationParams + extends PlatformNavigationDelegateCreationParams { + /// Creates a new [AndroidNavigationDelegateCreationParams] instance. + const AndroidNavigationDelegateCreationParams._({ + @visibleForTesting this.androidWebViewProxy = const AndroidWebViewProxy(), + }) : super(); + + /// Creates a [AndroidNavigationDelegateCreationParams] instance based on [PlatformNavigationDelegateCreationParams]. + factory AndroidNavigationDelegateCreationParams.fromPlatformNavigationDelegateCreationParams( + // Recommended placeholder to prevent being broken by platform interface. + // ignore: avoid_unused_constructor_parameters + PlatformNavigationDelegateCreationParams params, { + @visibleForTesting + AndroidWebViewProxy androidWebViewProxy = const AndroidWebViewProxy(), + }) { + return AndroidNavigationDelegateCreationParams._( + androidWebViewProxy: androidWebViewProxy, + ); + } + + /// Handles constructing objects and calling static methods for the Android WebView + /// native library. + @visibleForTesting + final AndroidWebViewProxy androidWebViewProxy; +} + +/// A place to register callback methods responsible to handle navigation events +/// triggered by the [android_webview.WebView]. +class AndroidNavigationDelegate extends PlatformNavigationDelegate { + /// Creates a new [AndroidNavigationDelegate]. + AndroidNavigationDelegate(PlatformNavigationDelegateCreationParams params) + : super.implementation(params is AndroidNavigationDelegateCreationParams + ? params + : AndroidNavigationDelegateCreationParams + .fromPlatformNavigationDelegateCreationParams(params)) { + final WeakReference weakThis = + WeakReference(this); + + _webViewClient = (this.params as AndroidNavigationDelegateCreationParams) + .androidWebViewProxy + .createAndroidWebViewClient( + onPageFinished: (android_webview.WebView webView, String url) { + if (weakThis.target?._onPageFinished != null) { + weakThis.target!._onPageFinished!(url); + } + }, + onPageStarted: (android_webview.WebView webView, String url) { + if (weakThis.target?._onPageStarted != null) { + weakThis.target!._onPageStarted!(url); + } + }, + onReceivedRequestError: ( + android_webview.WebView webView, + android_webview.WebResourceRequest request, + android_webview.WebResourceError error, + ) { + if (weakThis.target?._onWebResourceError != null) { + weakThis.target!._onWebResourceError!(AndroidWebResourceError._( + errorCode: error.errorCode, + description: error.description, + failingUrl: request.url, + isForMainFrame: request.isForMainFrame, + )); + } + }, + onReceivedError: ( + android_webview.WebView webView, + int errorCode, + String description, + String failingUrl, + ) { + if (weakThis.target?._onWebResourceError != null) { + weakThis.target!._onWebResourceError!(AndroidWebResourceError._( + errorCode: errorCode, + description: description, + failingUrl: failingUrl, + isForMainFrame: true, + )); + } + }, + requestLoading: ( + android_webview.WebView webView, + android_webview.WebResourceRequest request, + ) { + if (weakThis.target != null) { + weakThis.target!._handleNavigation( + request.url, + headers: request.requestHeaders, + isForMainFrame: request.isForMainFrame, + ); + } + }, + urlLoading: ( + android_webview.WebView webView, + String url, + ) { + if (weakThis.target != null) { + weakThis.target!._handleNavigation(url, isForMainFrame: true); + } + }, + ); + + _downloadListener = (this.params as AndroidNavigationDelegateCreationParams) + .androidWebViewProxy + .createDownloadListener( + onDownloadStart: ( + String url, + String userAgent, + String contentDisposition, + String mimetype, + int contentLength, + ) { + if (weakThis.target != null) { + weakThis.target?._handleNavigation(url, isForMainFrame: true); + } + }, + ); + } + + AndroidNavigationDelegateCreationParams get _androidParams => + params as AndroidNavigationDelegateCreationParams; + + late final android_webview.WebChromeClient _webChromeClient = + _androidParams.androidWebViewProxy.createAndroidWebChromeClient(); + + /// Gets the native [android_webview.WebChromeClient] that is bridged by this [AndroidNavigationDelegate]. + /// + /// Used by the [AndroidWebViewController] to set the `android_webview.WebView.setWebChromeClient`. + @Deprecated( + 'This value is not used by `AndroidWebViewController` and has no effect on the `WebView`.', + ) + android_webview.WebChromeClient get androidWebChromeClient => + _webChromeClient; + + late final android_webview.WebViewClient _webViewClient; + + /// Gets the native [android_webview.WebViewClient] that is bridged by this [AndroidNavigationDelegate]. + /// + /// Used by the [AndroidWebViewController] to set the `android_webview.WebView.setWebViewClient`. + android_webview.WebViewClient get androidWebViewClient => _webViewClient; + + late final android_webview.DownloadListener _downloadListener; + + /// Gets the native [android_webview.DownloadListener] that is bridged by this [AndroidNavigationDelegate]. + /// + /// Used by the [AndroidWebViewController] to set the `android_webview.WebView.setDownloadListener`. + android_webview.DownloadListener get androidDownloadListener => + _downloadListener; + + PageEventCallback? _onPageFinished; + PageEventCallback? _onPageStarted; + ProgressCallback? _onProgress; + WebResourceErrorCallback? _onWebResourceError; + NavigationRequestCallback? _onNavigationRequest; + LoadRequestCallback? _onLoadRequest; + + void _handleNavigation( + String url, { + required bool isForMainFrame, + Map headers = const {}, + }) { + final LoadRequestCallback? onLoadRequest = _onLoadRequest; + final NavigationRequestCallback? onNavigationRequest = _onNavigationRequest; + + if (onNavigationRequest == null || onLoadRequest == null) { + return; + } + + final FutureOr returnValue = onNavigationRequest( + NavigationRequest( + url: url, + isMainFrame: isForMainFrame, + ), + ); + + if (returnValue is NavigationDecision && + returnValue == NavigationDecision.navigate) { + onLoadRequest(LoadRequestParams( + uri: Uri.parse(url), + headers: headers, + )); + } else if (returnValue is Future) { + returnValue.then((NavigationDecision shouldLoadUrl) { + if (shouldLoadUrl == NavigationDecision.navigate) { + onLoadRequest(LoadRequestParams( + uri: Uri.parse(url), + headers: headers, + )); + } + }); + } + } + + /// Invoked when loading the url after a navigation request is approved. + Future setOnLoadRequest( + LoadRequestCallback onLoadRequest, + ) async { + _onLoadRequest = onLoadRequest; + } + + @override + Future setOnNavigationRequest( + NavigationRequestCallback onNavigationRequest, + ) async { + _onNavigationRequest = onNavigationRequest; + _webViewClient.setSynchronousReturnValueForShouldOverrideUrlLoading(true); + } + + @override + Future setOnPageStarted( + PageEventCallback onPageStarted, + ) async { + _onPageStarted = onPageStarted; + } + + @override + Future setOnPageFinished( + PageEventCallback onPageFinished, + ) async { + _onPageFinished = onPageFinished; + } + + @override + Future setOnProgress( + ProgressCallback onProgress, + ) async { + _onProgress = onProgress; + } + + @override + Future setOnWebResourceError( + WebResourceErrorCallback onWebResourceError, + ) async { + _onWebResourceError = onWebResourceError; + } +} diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_platform.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_platform.dart index 24581ebd24dc..7997f69d7eba 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_platform.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_platform.dart @@ -4,7 +4,6 @@ import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; -import 'android_navigation_delegate.dart'; import 'android_webview_controller.dart'; import 'android_webview_cookie_manager.dart'; diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/legacy/webview_android_widget.dart b/packages/webview_flutter/webview_flutter_android/lib/src/legacy/webview_android_widget.dart index fc1028e7af15..7e4ae9e6f9d1 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/legacy/webview_android_widget.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/legacy/webview_android_widget.dart @@ -134,7 +134,7 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { final Map _javaScriptChannels = {}; - late final android_webview.WebViewClient _webViewClient = withWeakRefenceTo( + late final android_webview.WebViewClient _webViewClient = withWeakReferenceTo( this, (WeakReference weakReference) { return webViewProxy.createWebViewClient( onPageStarted: (_, String url) { @@ -213,7 +213,7 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { @visibleForTesting late final android_webview.DownloadListener downloadListener = android_webview.DownloadListener( - onDownloadStart: withWeakRefenceTo( + onDownloadStart: withWeakReferenceTo( this, (WeakReference weakReference) { return ( @@ -236,7 +236,7 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { @visibleForTesting late final android_webview.WebChromeClient webChromeClient = android_webview.WebChromeClient( - onProgressChanged: withWeakRefenceTo( + onProgressChanged: withWeakReferenceTo( this, (WeakReference weakReference) { return (_, int progress) { @@ -574,7 +574,7 @@ class WebViewAndroidJavaScriptChannel super.channelName, this.javascriptChannelRegistry, ) : super( - postMessage: withWeakRefenceTo( + postMessage: withWeakReferenceTo( javascriptChannelRegistry, (WeakReference weakReference) { return (String message) { diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/weak_reference_utils.dart b/packages/webview_flutter/webview_flutter_android/lib/src/weak_reference_utils.dart index ad0c9ebf4f5c..fd3e3f0dc273 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/weak_reference_utils.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/weak_reference_utils.dart @@ -25,7 +25,7 @@ /// ), /// ); /// ``` -S withWeakRefenceTo( +S withWeakReferenceTo( T reference, S Function(WeakReference weakReference) onCreate, ) { diff --git a/packages/webview_flutter/webview_flutter_android/lib/webview_flutter_android.dart b/packages/webview_flutter/webview_flutter_android/lib/webview_flutter_android.dart index faab71549995..95f835ed8a1d 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/webview_flutter_android.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/webview_flutter_android.dart @@ -4,7 +4,6 @@ library webview_flutter_android; -export 'src/android_navigation_delegate.dart'; export 'src/android_webview_controller.dart'; export 'src/android_webview_cookie_manager.dart'; export 'src/android_webview_platform.dart'; diff --git a/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart b/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart index d3adac8ee4c4..29d3181f0fbf 100644 --- a/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart +++ b/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart @@ -26,6 +26,34 @@ import 'package:pigeon/pigeon.dart'; ), ), ) + +/// Mode of how to select files for a file chooser. +/// +/// See https://developer.android.com/reference/android/webkit/WebChromeClient.FileChooserParams. +enum FileChooserMode { + /// Open single file and requires that the file exists before allowing the + /// user to pick it. + /// + /// See https://developer.android.com/reference/android/webkit/WebChromeClient.FileChooserParams#MODE_OPEN. + open, + + /// Similar to [open] but allows multiple files to be selected. + /// + /// See https://developer.android.com/reference/android/webkit/WebChromeClient.FileChooserParams#MODE_OPEN_MULTIPLE. + openMultiple, + + /// Allows picking a nonexistent file and saving it. + /// + /// See https://developer.android.com/reference/android/webkit/WebChromeClient.FileChooserParams#MODE_SAVE. + save, +} + +// TODO(bparrishMines): Enums need be wrapped in a data class because thay can't +// be used as primitive arguments. See https://github.com/flutter/flutter/issues/87307 +class FileChooserModeEnumData { + late FileChooserMode value; +} + class WebResourceRequestData { WebResourceRequestData( this.url, @@ -262,6 +290,11 @@ abstract class DownloadListenerFlutterApi { @HostApi(dartHostTestHandler: 'TestWebChromeClientHostApi') abstract class WebChromeClientHostApi { void create(int instanceId); + + void setSynchronousReturnValueForOnShowFileChooser( + int instanceId, + bool value, + ); } @HostApi(dartHostTestHandler: 'TestAssetManagerHostApi') @@ -274,6 +307,13 @@ abstract class FlutterAssetManagerHostApi { @FlutterApi() abstract class WebChromeClientFlutterApi { void onProgressChanged(int instanceId, int webViewInstanceId, int progress); + + @async + List onShowFileChooser( + int instanceId, + int webViewInstanceId, + int paramsInstanceId, + ); } @HostApi(dartHostTestHandler: 'TestWebStorageHostApi') @@ -282,3 +322,17 @@ abstract class WebStorageHostApi { void deleteAllData(int instanceId); } + +/// Handles callbacks methods for the native Java FileChooserParams class. +/// +/// See https://developer.android.com/reference/android/webkit/WebChromeClient.FileChooserParams. +@FlutterApi() +abstract class FileChooserParamsFlutterApi { + void create( + int instanceId, + bool isCaptureEnabled, + List acceptTypes, + FileChooserModeEnumData mode, + String? filenameHint, + ); +} diff --git a/packages/webview_flutter/webview_flutter_android/pubspec.yaml b/packages/webview_flutter/webview_flutter_android/pubspec.yaml index 123a7c918870..b0088e22f403 100644 --- a/packages/webview_flutter/webview_flutter_android/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_android/pubspec.yaml @@ -2,7 +2,7 @@ name: webview_flutter_android description: A Flutter plugin that provides a WebView widget on Android. repository: https://github.com/flutter/plugins/tree/main/packages/webview_flutter/webview_flutter_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22 -version: 3.1.3 +version: 3.2.0 environment: sdk: ">=2.17.0 <3.0.0" @@ -29,4 +29,4 @@ dev_dependencies: flutter_test: sdk: flutter mockito: ^5.3.2 - pigeon: ^4.2.3 + pigeon: ^4.2.14 diff --git a/packages/webview_flutter/webview_flutter_android/test/android_navigation_delegate_test.dart b/packages/webview_flutter/webview_flutter_android/test/android_navigation_delegate_test.dart index 26d4e686d389..dac7c69a84f3 100644 --- a/packages/webview_flutter/webview_flutter_android/test/android_navigation_delegate_test.dart +++ b/packages/webview_flutter/webview_flutter_android/test/android_navigation_delegate_test.dart @@ -346,22 +346,6 @@ void main() { isTrue); }); - test('onProgress', () { - final AndroidNavigationDelegate androidNavigationDelegate = - AndroidNavigationDelegate(_buildCreationParams()); - - late final int callbackProgress; - androidNavigationDelegate - .setOnProgress((int progress) => callbackProgress = progress); - - CapturingWebChromeClient.lastCreatedDelegate.onProgressChanged!( - android_webview.WebView.detached(), - 42, - ); - - expect(callbackProgress, 42); - }); - test( 'onLoadRequest from onDownloadStart should not be called when navigationRequestCallback is not specified', () { @@ -495,6 +479,7 @@ class CapturingWebViewClient extends android_webview.WebViewClient { class CapturingWebChromeClient extends android_webview.WebChromeClient { CapturingWebChromeClient({ super.onProgressChanged, + super.onShowFileChooser, }) : super.detached() { lastCreatedDelegate = this; } diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.dart index 0e7842ab467d..80fa1924487c 100644 --- a/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.dart +++ b/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.dart @@ -19,6 +19,7 @@ import 'package:webview_flutter_android/src/platform_views_service_proxy.dart'; import 'package:webview_flutter_android/webview_flutter_android.dart'; import 'package:webview_flutter_platform_interface/src/webview_platform.dart'; +import 'android_navigation_delegate_test.dart'; import 'android_webview_controller_test.mocks.dart'; @GenerateNiceMocks(>[ @@ -44,7 +45,16 @@ void main() { AndroidWebViewController createControllerWithMocks({ android_webview.FlutterAssetManager? mockFlutterAssetManager, android_webview.JavaScriptChannel? mockJavaScriptChannel, - android_webview.WebChromeClient? mockWebChromeClient, + android_webview.WebChromeClient Function({ + void Function(android_webview.WebView webView, int progress)? + onProgressChanged, + Future> Function( + android_webview.WebView webView, + android_webview.FileChooserParams params, + )? + onShowFileChooser, + })? + createWebChromeClient, android_webview.WebView? mockWebView, android_webview.WebViewClient? mockWebViewClient, android_webview.WebStorage? mockWebStorage, @@ -57,10 +67,17 @@ void main() { AndroidWebViewControllerCreationParams( androidWebStorage: mockWebStorage ?? MockWebStorage(), androidWebViewProxy: AndroidWebViewProxy( - createAndroidWebChromeClient: ( - {void Function(android_webview.WebView, int)? - onProgressChanged}) => - mockWebChromeClient ?? MockWebChromeClient(), + createAndroidWebChromeClient: createWebChromeClient ?? + ({ + void Function(android_webview.WebView, int)? + onProgressChanged, + Future> Function( + android_webview.WebView webView, + android_webview.FileChooserParams params, + )? + onShowFileChooser, + }) => + MockWebChromeClient(), createAndroidWebView: ({required bool useHybridComposition}) => nonNullMockWebView, createAndroidWebViewClient: ({ @@ -486,10 +503,88 @@ void main() { await controller.setPlatformNavigationDelegate(mockNavigationDelegate); - verifyInOrder([ - mockWebView.setWebViewClient(mockWebViewClient), - mockWebView.setWebChromeClient(mockWebChromeClient), - ]); + verify(mockWebView.setWebViewClient(mockWebViewClient)); + verifyNever(mockWebView.setWebChromeClient(mockWebChromeClient)); + }); + + test('onProgress', () { + final AndroidNavigationDelegate androidNavigationDelegate = + AndroidNavigationDelegate( + AndroidNavigationDelegateCreationParams + .fromPlatformNavigationDelegateCreationParams( + const PlatformNavigationDelegateCreationParams(), + androidWebViewProxy: const AndroidWebViewProxy( + createAndroidWebViewClient: android_webview.WebViewClient.detached, + createAndroidWebChromeClient: + android_webview.WebChromeClient.detached, + createDownloadListener: android_webview.DownloadListener.detached, + ), + ), + ); + + late final int callbackProgress; + androidNavigationDelegate + .setOnProgress((int progress) => callbackProgress = progress); + + final AndroidWebViewController controller = createControllerWithMocks( + createWebChromeClient: CapturingWebChromeClient.new, + ); + controller.setPlatformNavigationDelegate(androidNavigationDelegate); + + CapturingWebChromeClient.lastCreatedDelegate.onProgressChanged!( + android_webview.WebView.detached(), + 42, + ); + + expect(callbackProgress, 42); + }); + + test('setOnShowFileSelector', () async { + late final Future> Function( + android_webview.WebView webView, + android_webview.FileChooserParams params, + ) onShowFileChooserCallback; + final MockWebChromeClient mockWebChromeClient = MockWebChromeClient(); + final AndroidWebViewController controller = createControllerWithMocks( + createWebChromeClient: ({ + dynamic onProgressChanged, + Future> Function( + android_webview.WebView webView, + android_webview.FileChooserParams params, + )? + onShowFileChooser, + }) { + onShowFileChooserCallback = onShowFileChooser!; + return mockWebChromeClient; + }, + ); + + late final FileSelectorParams fileSelectorParams; + await controller.setOnShowFileSelector( + (FileSelectorParams params) async { + fileSelectorParams = params; + return []; + }, + ); + + verify( + mockWebChromeClient.setSynchronousReturnValueForOnShowFileChooser(true), + ); + + onShowFileChooserCallback( + android_webview.WebView.detached(), + android_webview.FileChooserParams.detached( + isCaptureEnabled: false, + acceptTypes: ['png'], + filenameHint: 'filenameHint', + mode: android_webview.FileChooserMode.open, + ), + ); + + expect(fileSelectorParams.isCaptureEnabled, isFalse); + expect(fileSelectorParams.acceptTypes, ['png']); + expect(fileSelectorParams.filenameHint, 'filenameHint'); + expect(fileSelectorParams.mode, FileSelectorMode.open); }); test('runJavaScript', () async { diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.mocks.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.mocks.dart index 643f5ac964b1..01885caff54c 100644 --- a/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.mocks.dart +++ b/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.mocks.dart @@ -4,20 +4,18 @@ // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:async' as _i9; -import 'dart:typed_data' as _i15; +import 'dart:typed_data' as _i14; import 'dart:ui' as _i4; -import 'package:flutter/foundation.dart' as _i12; -import 'package:flutter/gestures.dart' as _i13; -import 'package:flutter/material.dart' as _i14; +import 'package:flutter/foundation.dart' as _i11; +import 'package:flutter/gestures.dart' as _i12; +import 'package:flutter/material.dart' as _i13; import 'package:flutter/services.dart' as _i7; import 'package:mockito/mockito.dart' as _i1; -import 'package:webview_flutter_android/src/android_navigation_delegate.dart' - as _i8; -import 'package:webview_flutter_android/src/android_proxy.dart' as _i11; +import 'package:webview_flutter_android/src/android_proxy.dart' as _i10; import 'package:webview_flutter_android/src/android_webview.dart' as _i2; import 'package:webview_flutter_android/src/android_webview_controller.dart' - as _i10; + as _i8; import 'package:webview_flutter_android/src/instance_manager.dart' as _i5; import 'package:webview_flutter_android/src/platform_views_service_proxy.dart' as _i6; @@ -349,7 +347,7 @@ class MockAndroidNavigationDelegate extends _i1.Mock /// /// See the documentation for Mockito's code generation for more information. class MockAndroidWebViewController extends _i1.Mock - implements _i10.AndroidWebViewController { + implements _i8.AndroidWebViewController { @override _i3.PlatformWebViewControllerCreationParams get params => (super.noSuchMethod( Invocation.getter(#params), @@ -649,13 +647,25 @@ class MockAndroidWebViewController extends _i1.Mock returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); + @override + _i9.Future setOnShowFileSelector( + _i9.Future> Function(_i8.FileSelectorParams)? + onShowFileSelectorCallback) => + (super.noSuchMethod( + Invocation.method( + #setOnShowFileSelector, + [onShowFileSelectorCallback], + ), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); } /// A class which mocks [AndroidWebViewProxy]. /// /// See the documentation for Mockito's code generation for more information. class MockAndroidWebViewProxy extends _i1.Mock - implements _i11.AndroidWebViewProxy { + implements _i10.AndroidWebViewProxy { @override _i2.WebView Function({required bool useHybridComposition}) get createAndroidWebView => (super.noSuchMethod( @@ -672,40 +682,63 @@ class MockAndroidWebViewProxy extends _i1.Mock ), ) as _i2.WebView Function({required bool useHybridComposition})); @override - _i2.WebChromeClient Function( - {void Function( - _i2.WebView, - int, - )? - onProgressChanged}) get createAndroidWebChromeClient => - (super.noSuchMethod( + _i2.WebChromeClient Function({ + void Function( + _i2.WebView, + int, + )? + onProgressChanged, + _i9.Future> Function( + _i2.WebView, + _i2.FileChooserParams, + )? + onShowFileChooser, + }) get createAndroidWebChromeClient => (super.noSuchMethod( Invocation.getter(#createAndroidWebChromeClient), - returnValue: ( - {void Function( - _i2.WebView, - int, - )? - onProgressChanged}) => + returnValue: ({ + void Function( + _i2.WebView, + int, + )? + onProgressChanged, + _i9.Future> Function( + _i2.WebView, + _i2.FileChooserParams, + )? + onShowFileChooser, + }) => _FakeWebChromeClient_0( this, Invocation.getter(#createAndroidWebChromeClient), ), - returnValueForMissingStub: ( - {void Function( - _i2.WebView, - int, - )? - onProgressChanged}) => + returnValueForMissingStub: ({ + void Function( + _i2.WebView, + int, + )? + onProgressChanged, + _i9.Future> Function( + _i2.WebView, + _i2.FileChooserParams, + )? + onShowFileChooser, + }) => _FakeWebChromeClient_0( this, Invocation.getter(#createAndroidWebChromeClient), ), - ) as _i2.WebChromeClient Function( - {void Function( - _i2.WebView, - int, - )? - onProgressChanged})); + ) as _i2.WebChromeClient Function({ + void Function( + _i2.WebView, + int, + )? + onProgressChanged, + _i9.Future> Function( + _i2.WebView, + _i2.FileChooserParams, + )? + onShowFileChooser, + })); @override _i2.WebViewClient Function({ void Function( @@ -958,7 +991,7 @@ class MockAndroidWebViewProxy extends _i1.Mock /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable class MockAndroidWebViewWidgetCreationParams extends _i1.Mock - implements _i10.AndroidWebViewWidgetCreationParams { + implements _i8.AndroidWebViewWidgetCreationParams { @override _i5.InstanceManager get instanceManager => (super.noSuchMethod( Invocation.getter(#instanceManager), @@ -1009,13 +1042,13 @@ class MockAndroidWebViewWidgetCreationParams extends _i1.Mock returnValueForMissingStub: _i4.TextDirection.rtl, ) as _i4.TextDirection); @override - Set<_i12.Factory<_i13.OneSequenceGestureRecognizer>> get gestureRecognizers => + Set<_i11.Factory<_i12.OneSequenceGestureRecognizer>> get gestureRecognizers => (super.noSuchMethod( Invocation.getter(#gestureRecognizers), - returnValue: <_i12.Factory<_i13.OneSequenceGestureRecognizer>>{}, + returnValue: <_i11.Factory<_i12.OneSequenceGestureRecognizer>>{}, returnValueForMissingStub: < - _i12.Factory<_i13.OneSequenceGestureRecognizer>>{}, - ) as Set<_i12.Factory<_i13.OneSequenceGestureRecognizer>>); + _i11.Factory<_i12.OneSequenceGestureRecognizer>>{}, + ) as Set<_i11.Factory<_i12.OneSequenceGestureRecognizer>>); } /// A class which mocks [ExpensiveAndroidViewController]. @@ -1162,7 +1195,7 @@ class MockExpensiveAndroidViewController extends _i1.Mock returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override - _i9.Future dispatchPointerEvent(_i14.PointerEvent? event) => + _i9.Future dispatchPointerEvent(_i13.PointerEvent? event) => (super.noSuchMethod( Invocation.method( #dispatchPointerEvent, @@ -1514,7 +1547,7 @@ class MockSurfaceAndroidViewController extends _i1.Mock returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override - _i9.Future dispatchPointerEvent(_i14.PointerEvent? event) => + _i9.Future dispatchPointerEvent(_i13.PointerEvent? event) => (super.noSuchMethod( Invocation.method( #dispatchPointerEvent, @@ -1548,6 +1581,16 @@ class MockSurfaceAndroidViewController extends _i1.Mock /// See the documentation for Mockito's code generation for more information. class MockWebChromeClient extends _i1.Mock implements _i2.WebChromeClient { @override + _i9.Future setSynchronousReturnValueForOnShowFileChooser(bool? value) => + (super.noSuchMethod( + Invocation.method( + #setSynchronousReturnValueForOnShowFileChooser, + [value], + ), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); + @override _i2.WebChromeClient copy() => (super.noSuchMethod( Invocation.method( #copy, @@ -1793,7 +1836,7 @@ class MockWebView extends _i1.Mock implements _i2.WebView { @override _i9.Future postUrl( String? url, - _i15.Uint8List? data, + _i14.Uint8List? data, ) => (super.noSuchMethod( Invocation.method( diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart index 4e972fe7b98d..bd01494cfb4d 100644 --- a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart +++ b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart @@ -831,10 +831,68 @@ void main() { expect(result, containsAllInOrder([mockWebView, 76])); }); + test('onShowFileChooser', () async { + late final List result; + when(mockWebChromeClient.onShowFileChooser).thenReturn( + (WebView webView, FileChooserParams params) { + result = [webView, params]; + return Future>.value(['fileOne', 'fileTwo']); + }, + ); + + final FileChooserParams params = FileChooserParams.detached( + isCaptureEnabled: false, + acceptTypes: [], + filenameHint: 'filenameHint', + mode: FileChooserMode.open, + ); + + instanceManager.addHostCreatedInstance(params, 3); + + await expectLater( + flutterApi.onShowFileChooser( + mockWebChromeClientInstanceId, + mockWebViewInstanceId, + 3, + ), + completion(['fileOne', 'fileTwo']), + ); + expect(result[0], mockWebView); + expect(result[1], params); + }); + test('copy', () { expect(WebChromeClient.detached().copy(), isA()); }); }); + + group('FileChooserParams', () { + test('FlutterApi create', () { + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: (_) {}, + ); + + final FileChooserParamsFlutterApiImpl flutterApi = + FileChooserParamsFlutterApiImpl( + instanceManager: instanceManager, + ); + + flutterApi.create( + 0, + false, + const ['my', 'list'], + FileChooserModeEnumData(value: FileChooserMode.openMultiple), + 'filenameHint', + ); + + final FileChooserParams instance = instanceManager + .getInstanceWithWeakReference(0)! as FileChooserParams; + expect(instance.isCaptureEnabled, false); + expect(instance.acceptTypes, const ['my', 'list']); + expect(instance.mode, FileChooserMode.openMultiple); + expect(instance.filenameHint, 'filenameHint'); + }); + }); }); group('CookieManager', () { diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart index 81cdc9ef545f..e3a2f108d8ec 100644 --- a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart +++ b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart @@ -304,6 +304,21 @@ class MockTestWebChromeClientHostApi extends _i1.Mock ), returnValueForMissingStub: null, ); + @override + void setSynchronousReturnValueForOnShowFileChooser( + int? instanceId, + bool? value, + ) => + super.noSuchMethod( + Invocation.method( + #setSynchronousReturnValueForOnShowFileChooser, + [ + instanceId, + value, + ], + ), + returnValueForMissingStub: null, + ); } /// A class which mocks [TestWebSettingsHostApi]. @@ -952,6 +967,16 @@ class MockWebChromeClient extends _i1.Mock implements _i2.WebChromeClient { _i1.throwOnMissingStub(this); } + @override + _i5.Future setSynchronousReturnValueForOnShowFileChooser(bool? value) => + (super.noSuchMethod( + Invocation.method( + #setSynchronousReturnValueForOnShowFileChooser, + [value], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); @override _i2.WebChromeClient copy() => (super.noSuchMethod( Invocation.method( diff --git a/packages/webview_flutter/webview_flutter_android/test/legacy/webview_android_widget_test.mocks.dart b/packages/webview_flutter/webview_flutter_android/test/legacy/webview_android_widget_test.mocks.dart index 1f16c29aa953..03489ce5c1e0 100644 --- a/packages/webview_flutter/webview_flutter_android/test/legacy/webview_android_widget_test.mocks.dart +++ b/packages/webview_flutter/webview_flutter_android/test/legacy/webview_android_widget_test.mocks.dart @@ -763,6 +763,16 @@ class MockWebChromeClient extends _i1.Mock implements _i2.WebChromeClient { _i1.throwOnMissingStub(this); } + @override + _i5.Future setSynchronousReturnValueForOnShowFileChooser(bool? value) => + (super.noSuchMethod( + Invocation.method( + #setSynchronousReturnValueForOnShowFileChooser, + [value], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); @override _i2.WebChromeClient copy() => (super.noSuchMethod( Invocation.method( diff --git a/packages/webview_flutter/webview_flutter_android/test/test_android_webview.pigeon.dart b/packages/webview_flutter/webview_flutter_android/test/test_android_webview.pigeon.dart index bcfb453e85c4..b5df3c1354d0 100644 --- a/packages/webview_flutter/webview_flutter_android/test/test_android_webview.pigeon.dart +++ b/packages/webview_flutter/webview_flutter_android/test/test_android_webview.pigeon.dart @@ -1,7 +1,7 @@ // Copyright 2013 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. -// Autogenerated from Pigeon (v4.2.3), do not edit directly. +// Autogenerated from Pigeon (v4.2.14), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, unnecessary_import // ignore_for_file: avoid_relative_lib_imports @@ -22,6 +22,7 @@ abstract class TestJavaObjectHostApi { static const MessageCodec codec = StandardMessageCodec(); void dispose(int identifier); + static void setup(TestJavaObjectHostApi? api, {BinaryMessenger? binaryMessenger}) { { @@ -39,7 +40,7 @@ abstract class TestJavaObjectHostApi { assert(arg_identifier != null, 'Argument for dev.flutter.pigeon.JavaObjectHostApi.dispose was null, expected non-null int.'); api.dispose(arg_identifier!); - return {}; + return []; }); } } @@ -74,33 +75,59 @@ abstract class TestWebViewHostApi { static const MessageCodec codec = _TestWebViewHostApiCodec(); void create(int instanceId, bool useHybridComposition); + void loadData( int instanceId, String data, String? mimeType, String? encoding); + void loadDataWithBaseUrl(int instanceId, String? baseUrl, String data, String? mimeType, String? encoding, String? historyUrl); + void loadUrl(int instanceId, String url, Map headers); + void postUrl(int instanceId, String url, Uint8List data); + String? getUrl(int instanceId); + bool canGoBack(int instanceId); + bool canGoForward(int instanceId); + void goBack(int instanceId); + void goForward(int instanceId); + void reload(int instanceId); + void clearCache(int instanceId, bool includeDiskFiles); + Future evaluateJavascript(int instanceId, String javascriptString); + String? getTitle(int instanceId); + void scrollTo(int instanceId, int x, int y); + void scrollBy(int instanceId, int x, int y); + int getScrollX(int instanceId); + int getScrollY(int instanceId); + WebViewPoint getScrollPosition(int instanceId); + void setWebContentsDebuggingEnabled(bool enabled); + void setWebViewClient(int instanceId, int webViewClientInstanceId); + void addJavaScriptChannel(int instanceId, int javaScriptChannelInstanceId); + void removeJavaScriptChannel(int instanceId, int javaScriptChannelInstanceId); + void setDownloadListener(int instanceId, int? listenerInstanceId); + void setWebChromeClient(int instanceId, int? clientInstanceId); + void setBackgroundColor(int instanceId, int color); + static void setup(TestWebViewHostApi? api, {BinaryMessenger? binaryMessenger}) { { @@ -121,7 +148,7 @@ abstract class TestWebViewHostApi { assert(arg_useHybridComposition != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.create was null, expected non-null bool.'); api.create(arg_instanceId!, arg_useHybridComposition!); - return {}; + return []; }); } } @@ -145,7 +172,7 @@ abstract class TestWebViewHostApi { final String? arg_mimeType = (args[2] as String?); final String? arg_encoding = (args[3] as String?); api.loadData(arg_instanceId!, arg_data!, arg_mimeType, arg_encoding); - return {}; + return []; }); } } @@ -172,7 +199,7 @@ abstract class TestWebViewHostApi { final String? arg_historyUrl = (args[5] as String?); api.loadDataWithBaseUrl(arg_instanceId!, arg_baseUrl, arg_data!, arg_mimeType, arg_encoding, arg_historyUrl); - return {}; + return []; }); } } @@ -198,7 +225,7 @@ abstract class TestWebViewHostApi { assert(arg_headers != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.loadUrl was null, expected non-null Map.'); api.loadUrl(arg_instanceId!, arg_url!, arg_headers!); - return {}; + return []; }); } } @@ -223,7 +250,7 @@ abstract class TestWebViewHostApi { assert(arg_data != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.postUrl was null, expected non-null Uint8List.'); api.postUrl(arg_instanceId!, arg_url!, arg_data!); - return {}; + return []; }); } } @@ -242,7 +269,7 @@ abstract class TestWebViewHostApi { assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.getUrl was null, expected non-null int.'); final String? output = api.getUrl(arg_instanceId!); - return {'result': output}; + return [output]; }); } } @@ -261,7 +288,7 @@ abstract class TestWebViewHostApi { assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.canGoBack was null, expected non-null int.'); final bool output = api.canGoBack(arg_instanceId!); - return {'result': output}; + return [output]; }); } } @@ -280,7 +307,7 @@ abstract class TestWebViewHostApi { assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.canGoForward was null, expected non-null int.'); final bool output = api.canGoForward(arg_instanceId!); - return {'result': output}; + return [output]; }); } } @@ -299,7 +326,7 @@ abstract class TestWebViewHostApi { assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.goBack was null, expected non-null int.'); api.goBack(arg_instanceId!); - return {}; + return []; }); } } @@ -318,7 +345,7 @@ abstract class TestWebViewHostApi { assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.goForward was null, expected non-null int.'); api.goForward(arg_instanceId!); - return {}; + return []; }); } } @@ -337,7 +364,7 @@ abstract class TestWebViewHostApi { assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.reload was null, expected non-null int.'); api.reload(arg_instanceId!); - return {}; + return []; }); } } @@ -359,7 +386,7 @@ abstract class TestWebViewHostApi { assert(arg_includeDiskFiles != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.clearCache was null, expected non-null bool.'); api.clearCache(arg_instanceId!, arg_includeDiskFiles!); - return {}; + return []; }); } } @@ -382,7 +409,7 @@ abstract class TestWebViewHostApi { 'Argument for dev.flutter.pigeon.WebViewHostApi.evaluateJavascript was null, expected non-null String.'); final String? output = await api.evaluateJavascript( arg_instanceId!, arg_javascriptString!); - return {'result': output}; + return [output]; }); } } @@ -401,7 +428,7 @@ abstract class TestWebViewHostApi { assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.getTitle was null, expected non-null int.'); final String? output = api.getTitle(arg_instanceId!); - return {'result': output}; + return [output]; }); } } @@ -426,7 +453,7 @@ abstract class TestWebViewHostApi { assert(arg_y != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.scrollTo was null, expected non-null int.'); api.scrollTo(arg_instanceId!, arg_x!, arg_y!); - return {}; + return []; }); } } @@ -451,7 +478,7 @@ abstract class TestWebViewHostApi { assert(arg_y != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.scrollBy was null, expected non-null int.'); api.scrollBy(arg_instanceId!, arg_x!, arg_y!); - return {}; + return []; }); } } @@ -470,7 +497,7 @@ abstract class TestWebViewHostApi { assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.getScrollX was null, expected non-null int.'); final int output = api.getScrollX(arg_instanceId!); - return {'result': output}; + return [output]; }); } } @@ -489,7 +516,7 @@ abstract class TestWebViewHostApi { assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.getScrollY was null, expected non-null int.'); final int output = api.getScrollY(arg_instanceId!); - return {'result': output}; + return [output]; }); } } @@ -508,7 +535,7 @@ abstract class TestWebViewHostApi { assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.getScrollPosition was null, expected non-null int.'); final WebViewPoint output = api.getScrollPosition(arg_instanceId!); - return {'result': output}; + return [output]; }); } } @@ -528,7 +555,7 @@ abstract class TestWebViewHostApi { assert(arg_enabled != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.setWebContentsDebuggingEnabled was null, expected non-null bool.'); api.setWebContentsDebuggingEnabled(arg_enabled!); - return {}; + return []; }); } } @@ -550,7 +577,7 @@ abstract class TestWebViewHostApi { assert(arg_webViewClientInstanceId != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.setWebViewClient was null, expected non-null int.'); api.setWebViewClient(arg_instanceId!, arg_webViewClientInstanceId!); - return {}; + return []; }); } } @@ -573,7 +600,7 @@ abstract class TestWebViewHostApi { 'Argument for dev.flutter.pigeon.WebViewHostApi.addJavaScriptChannel was null, expected non-null int.'); api.addJavaScriptChannel( arg_instanceId!, arg_javaScriptChannelInstanceId!); - return {}; + return []; }); } } @@ -596,7 +623,7 @@ abstract class TestWebViewHostApi { 'Argument for dev.flutter.pigeon.WebViewHostApi.removeJavaScriptChannel was null, expected non-null int.'); api.removeJavaScriptChannel( arg_instanceId!, arg_javaScriptChannelInstanceId!); - return {}; + return []; }); } } @@ -616,7 +643,7 @@ abstract class TestWebViewHostApi { 'Argument for dev.flutter.pigeon.WebViewHostApi.setDownloadListener was null, expected non-null int.'); final int? arg_listenerInstanceId = (args[1] as int?); api.setDownloadListener(arg_instanceId!, arg_listenerInstanceId); - return {}; + return []; }); } } @@ -636,7 +663,7 @@ abstract class TestWebViewHostApi { 'Argument for dev.flutter.pigeon.WebViewHostApi.setWebChromeClient was null, expected non-null int.'); final int? arg_clientInstanceId = (args[1] as int?); api.setWebChromeClient(arg_instanceId!, arg_clientInstanceId); - return {}; + return []; }); } } @@ -658,7 +685,7 @@ abstract class TestWebViewHostApi { assert(arg_color != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.setBackgroundColor was null, expected non-null int.'); api.setBackgroundColor(arg_instanceId!, arg_color!); - return {}; + return []; }); } } @@ -669,18 +696,31 @@ abstract class TestWebSettingsHostApi { static const MessageCodec codec = StandardMessageCodec(); void create(int instanceId, int webViewInstanceId); + void setDomStorageEnabled(int instanceId, bool flag); + void setJavaScriptCanOpenWindowsAutomatically(int instanceId, bool flag); + void setSupportMultipleWindows(int instanceId, bool support); + void setJavaScriptEnabled(int instanceId, bool flag); + void setUserAgentString(int instanceId, String? userAgentString); + void setMediaPlaybackRequiresUserGesture(int instanceId, bool require); + void setSupportZoom(int instanceId, bool support); + void setLoadWithOverviewMode(int instanceId, bool overview); + void setUseWideViewPort(int instanceId, bool use); + void setDisplayZoomControls(int instanceId, bool enabled); + void setBuiltInZoomControls(int instanceId, bool enabled); + void setAllowFileAccess(int instanceId, bool enabled); + static void setup(TestWebSettingsHostApi? api, {BinaryMessenger? binaryMessenger}) { { @@ -701,7 +741,7 @@ abstract class TestWebSettingsHostApi { assert(arg_webViewInstanceId != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.create was null, expected non-null int.'); api.create(arg_instanceId!, arg_webViewInstanceId!); - return {}; + return []; }); } } @@ -723,7 +763,7 @@ abstract class TestWebSettingsHostApi { assert(arg_flag != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setDomStorageEnabled was null, expected non-null bool.'); api.setDomStorageEnabled(arg_instanceId!, arg_flag!); - return {}; + return []; }); } } @@ -747,7 +787,7 @@ abstract class TestWebSettingsHostApi { 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setJavaScriptCanOpenWindowsAutomatically was null, expected non-null bool.'); api.setJavaScriptCanOpenWindowsAutomatically( arg_instanceId!, arg_flag!); - return {}; + return []; }); } } @@ -770,7 +810,7 @@ abstract class TestWebSettingsHostApi { assert(arg_support != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setSupportMultipleWindows was null, expected non-null bool.'); api.setSupportMultipleWindows(arg_instanceId!, arg_support!); - return {}; + return []; }); } } @@ -792,7 +832,7 @@ abstract class TestWebSettingsHostApi { assert(arg_flag != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setJavaScriptEnabled was null, expected non-null bool.'); api.setJavaScriptEnabled(arg_instanceId!, arg_flag!); - return {}; + return []; }); } } @@ -812,7 +852,7 @@ abstract class TestWebSettingsHostApi { 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setUserAgentString was null, expected non-null int.'); final String? arg_userAgentString = (args[1] as String?); api.setUserAgentString(arg_instanceId!, arg_userAgentString); - return {}; + return []; }); } } @@ -836,7 +876,7 @@ abstract class TestWebSettingsHostApi { 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setMediaPlaybackRequiresUserGesture was null, expected non-null bool.'); api.setMediaPlaybackRequiresUserGesture( arg_instanceId!, arg_require!); - return {}; + return []; }); } } @@ -858,7 +898,7 @@ abstract class TestWebSettingsHostApi { assert(arg_support != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setSupportZoom was null, expected non-null bool.'); api.setSupportZoom(arg_instanceId!, arg_support!); - return {}; + return []; }); } } @@ -881,7 +921,7 @@ abstract class TestWebSettingsHostApi { assert(arg_overview != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setLoadWithOverviewMode was null, expected non-null bool.'); api.setLoadWithOverviewMode(arg_instanceId!, arg_overview!); - return {}; + return []; }); } } @@ -903,7 +943,7 @@ abstract class TestWebSettingsHostApi { assert(arg_use != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setUseWideViewPort was null, expected non-null bool.'); api.setUseWideViewPort(arg_instanceId!, arg_use!); - return {}; + return []; }); } } @@ -925,7 +965,7 @@ abstract class TestWebSettingsHostApi { assert(arg_enabled != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setDisplayZoomControls was null, expected non-null bool.'); api.setDisplayZoomControls(arg_instanceId!, arg_enabled!); - return {}; + return []; }); } } @@ -947,7 +987,7 @@ abstract class TestWebSettingsHostApi { assert(arg_enabled != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setBuiltInZoomControls was null, expected non-null bool.'); api.setBuiltInZoomControls(arg_instanceId!, arg_enabled!); - return {}; + return []; }); } } @@ -969,7 +1009,7 @@ abstract class TestWebSettingsHostApi { assert(arg_enabled != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setAllowFileAccess was null, expected non-null bool.'); api.setAllowFileAccess(arg_instanceId!, arg_enabled!); - return {}; + return []; }); } } @@ -980,6 +1020,7 @@ abstract class TestJavaScriptChannelHostApi { static const MessageCodec codec = StandardMessageCodec(); void create(int instanceId, String channelName); + static void setup(TestJavaScriptChannelHostApi? api, {BinaryMessenger? binaryMessenger}) { { @@ -1000,7 +1041,7 @@ abstract class TestJavaScriptChannelHostApi { assert(arg_channelName != null, 'Argument for dev.flutter.pigeon.JavaScriptChannelHostApi.create was null, expected non-null String.'); api.create(arg_instanceId!, arg_channelName!); - return {}; + return []; }); } } @@ -1011,8 +1052,10 @@ abstract class TestWebViewClientHostApi { static const MessageCodec codec = StandardMessageCodec(); void create(int instanceId); + void setSynchronousReturnValueForShouldOverrideUrlLoading( int instanceId, bool value); + static void setup(TestWebViewClientHostApi? api, {BinaryMessenger? binaryMessenger}) { { @@ -1030,7 +1073,7 @@ abstract class TestWebViewClientHostApi { assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebViewClientHostApi.create was null, expected non-null int.'); api.create(arg_instanceId!); - return {}; + return []; }); } } @@ -1054,7 +1097,7 @@ abstract class TestWebViewClientHostApi { 'Argument for dev.flutter.pigeon.WebViewClientHostApi.setSynchronousReturnValueForShouldOverrideUrlLoading was null, expected non-null bool.'); api.setSynchronousReturnValueForShouldOverrideUrlLoading( arg_instanceId!, arg_value!); - return {}; + return []; }); } } @@ -1065,6 +1108,7 @@ abstract class TestDownloadListenerHostApi { static const MessageCodec codec = StandardMessageCodec(); void create(int instanceId); + static void setup(TestDownloadListenerHostApi? api, {BinaryMessenger? binaryMessenger}) { { @@ -1082,7 +1126,7 @@ abstract class TestDownloadListenerHostApi { assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.DownloadListenerHostApi.create was null, expected non-null int.'); api.create(arg_instanceId!); - return {}; + return []; }); } } @@ -1093,6 +1137,10 @@ abstract class TestWebChromeClientHostApi { static const MessageCodec codec = StandardMessageCodec(); void create(int instanceId); + + void setSynchronousReturnValueForOnShowFileChooser( + int instanceId, bool value); + static void setup(TestWebChromeClientHostApi? api, {BinaryMessenger? binaryMessenger}) { { @@ -1110,7 +1158,31 @@ abstract class TestWebChromeClientHostApi { assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebChromeClientHostApi.create was null, expected non-null int.'); api.create(arg_instanceId!); - return {}; + return []; + }); + } + } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.WebChromeClientHostApi.setSynchronousReturnValueForOnShowFileChooser', + codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMockMessageHandler(null); + } else { + channel.setMockMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.WebChromeClientHostApi.setSynchronousReturnValueForOnShowFileChooser was null.'); + final List args = (message as List?)!; + final int? arg_instanceId = (args[0] as int?); + assert(arg_instanceId != null, + 'Argument for dev.flutter.pigeon.WebChromeClientHostApi.setSynchronousReturnValueForOnShowFileChooser was null, expected non-null int.'); + final bool? arg_value = (args[1] as bool?); + assert(arg_value != null, + 'Argument for dev.flutter.pigeon.WebChromeClientHostApi.setSynchronousReturnValueForOnShowFileChooser was null, expected non-null bool.'); + api.setSynchronousReturnValueForOnShowFileChooser( + arg_instanceId!, arg_value!); + return []; }); } } @@ -1121,7 +1193,9 @@ abstract class TestAssetManagerHostApi { static const MessageCodec codec = StandardMessageCodec(); List list(String path); + String getAssetFilePathByName(String name); + static void setup(TestAssetManagerHostApi? api, {BinaryMessenger? binaryMessenger}) { { @@ -1139,7 +1213,7 @@ abstract class TestAssetManagerHostApi { assert(arg_path != null, 'Argument for dev.flutter.pigeon.FlutterAssetManagerHostApi.list was null, expected non-null String.'); final List output = api.list(arg_path!); - return {'result': output}; + return [output]; }); } } @@ -1159,7 +1233,7 @@ abstract class TestAssetManagerHostApi { assert(arg_name != null, 'Argument for dev.flutter.pigeon.FlutterAssetManagerHostApi.getAssetFilePathByName was null, expected non-null String.'); final String output = api.getAssetFilePathByName(arg_name!); - return {'result': output}; + return [output]; }); } } @@ -1170,7 +1244,9 @@ abstract class TestWebStorageHostApi { static const MessageCodec codec = StandardMessageCodec(); void create(int instanceId); + void deleteAllData(int instanceId); + static void setup(TestWebStorageHostApi? api, {BinaryMessenger? binaryMessenger}) { { @@ -1188,7 +1264,7 @@ abstract class TestWebStorageHostApi { assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebStorageHostApi.create was null, expected non-null int.'); api.create(arg_instanceId!); - return {}; + return []; }); } } @@ -1207,7 +1283,7 @@ abstract class TestWebStorageHostApi { assert(arg_instanceId != null, 'Argument for dev.flutter.pigeon.WebStorageHostApi.deleteAllData was null, expected non-null int.'); api.deleteAllData(arg_instanceId!); - return {}; + return []; }); } } From ffb36e0ae0620c6072521692834b1e417a47b4cb Mon Sep 17 00:00:00 2001 From: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> Date: Thu, 19 Jan 2023 22:09:09 -0500 Subject: [PATCH 012/130] [webview_flutter_android] Fix throwing `StateError` when `onShowFileChooser` was nonnull (#6995) --- .../lib/src/android_webview.dart | 2 +- .../test/android_webview_test.dart | 52 +++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart index f7d536ce3972..c5d301d93202 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart @@ -927,7 +927,7 @@ class WebChromeClient extends JavaObject { Future setSynchronousReturnValueForOnShowFileChooser( bool value, ) { - if (value && onShowFileChooser != null) { + if (value && onShowFileChooser == null) { throw StateError( 'Setting this to true requires `onShowFileChooser` to be nonnull.', ); diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart index bd01494cfb4d..9878d7a7e5cf 100644 --- a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart +++ b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart @@ -861,6 +861,58 @@ void main() { expect(result[1], params); }); + test('setSynchronousReturnValueForOnShowFileChooser', () { + final MockTestWebChromeClientHostApi mockHostApi = + MockTestWebChromeClientHostApi(); + TestWebChromeClientHostApi.setup(mockHostApi); + + WebChromeClient.api = + WebChromeClientHostApiImpl(instanceManager: instanceManager); + + final WebChromeClient webChromeClient = WebChromeClient.detached(); + instanceManager.addHostCreatedInstance(webChromeClient, 2); + + webChromeClient.setSynchronousReturnValueForOnShowFileChooser(false); + + verify( + mockHostApi.setSynchronousReturnValueForOnShowFileChooser(2, false), + ); + }); + + test( + 'setSynchronousReturnValueForOnShowFileChooser throws StateError when onShowFileChooser is null', + () { + final MockTestWebChromeClientHostApi mockHostApi = + MockTestWebChromeClientHostApi(); + TestWebChromeClientHostApi.setup(mockHostApi); + + WebChromeClient.api = + WebChromeClientHostApiImpl(instanceManager: instanceManager); + + final WebChromeClient clientWithNullCallback = + WebChromeClient.detached(); + instanceManager.addHostCreatedInstance(clientWithNullCallback, 2); + + expect( + () => clientWithNullCallback + .setSynchronousReturnValueForOnShowFileChooser(true), + throwsStateError, + ); + + final WebChromeClient clientWithNonnullCallback = + WebChromeClient.detached( + onShowFileChooser: (_, __) async => [], + ); + instanceManager.addHostCreatedInstance(clientWithNonnullCallback, 3); + + clientWithNonnullCallback + .setSynchronousReturnValueForOnShowFileChooser(true); + + verify( + mockHostApi.setSynchronousReturnValueForOnShowFileChooser(3, true), + ); + }); + test('copy', () { expect(WebChromeClient.detached().copy(), isA()); }); From 783ce6d8f05ecb4da19b90bea063fff5278b551f Mon Sep 17 00:00:00 2001 From: stuartmorgan Date: Fri, 20 Jan 2023 08:03:52 -0800 Subject: [PATCH 013/130] [various] More analysis_options alignment (#6949) * Change the way local changes are handled to match packages * only_throw_errors * Enable no_default_cases * Fix violations in camera * Fix violations in webview * Fix url_launcher violation * Fix violations in shared_preferences * Fix violations in maps * Version bumps * Fix image_picker violations * Fix video_player violations * New version bumps * Update excerpts * Address review feedback --- analysis_options.yaml | 13 ++++--------- packages/camera/camera/CHANGELOG.md | 6 +++++- packages/camera/camera/example/lib/main.dart | 6 ++++-- packages/camera/camera/pubspec.yaml | 2 +- packages/camera/camera_android/CHANGELOG.md | 6 +++++- .../camera_android/example/lib/main.dart | 6 ++++-- .../lib/src/android_camera.dart | 19 +++++++++++++++---- .../camera/camera_android/lib/src/utils.dart | 9 +++++++-- packages/camera/camera_android/pubspec.yaml | 2 +- .../camera/camera_avfoundation/CHANGELOG.md | 4 ++++ .../camera_avfoundation/example/lib/main.dart | 6 ++++-- .../lib/src/avfoundation_camera.dart | 19 +++++++++++++++---- .../camera_avfoundation/lib/src/utils.dart | 9 +++++++-- .../camera/camera_avfoundation/pubspec.yaml | 2 +- .../camera_platform_interface/CHANGELOG.md | 4 ++++ .../method_channel/method_channel_camera.dart | 5 +---- .../lib/src/types/exposure_mode.dart | 2 -- .../lib/src/types/focus_mode.dart | 2 -- .../lib/src/types/image_format_group.dart | 1 - .../lib/src/utils/utils.dart | 2 -- .../camera_platform_interface/pubspec.yaml | 2 +- packages/camera/camera_web/CHANGELOG.md | 4 ++++ .../camera_web/lib/src/camera_service.dart | 8 +++++++- packages/camera/camera_web/pubspec.yaml | 2 +- .../google_maps_flutter_android/CHANGELOG.md | 4 ++++ .../lib/src/google_maps_flutter_android.dart | 2 +- .../google_maps_flutter_android/pubspec.yaml | 2 +- .../google_maps_flutter_web/CHANGELOG.md | 4 ++++ .../lib/src/convert.dart | 8 +++++++- .../google_maps_flutter_web/pubspec.yaml | 2 +- .../image_picker/image_picker/CHANGELOG.md | 4 ++++ .../image_picker/example/lib/main.dart | 2 +- .../image_picker/image_picker/pubspec.yaml | 2 +- .../image_picker_android/CHANGELOG.md | 4 ++++ .../example/lib/main.dart | 2 +- .../image_picker_android/pubspec.yaml | 2 +- .../image_picker_ios/CHANGELOG.md | 4 ++++ .../lib/image_picker_ios.dart | 18 ++++++++++++++---- .../image_picker_ios/pubspec.yaml | 2 +- .../sk_methodchannel_apis_test.dart | 2 +- .../shared_preferences/CHANGELOG.md | 4 ++++ .../shared_preferences/example/lib/main.dart | 4 +++- .../shared_preferences/pubspec.yaml | 2 +- .../shared_preferences_android/CHANGELOG.md | 4 ++++ .../example/lib/main.dart | 4 +++- .../shared_preferences_android/pubspec.yaml | 2 +- .../CHANGELOG.md | 4 ++++ .../example/lib/main.dart | 4 +++- .../pubspec.yaml | 2 +- .../shared_preferences_linux/CHANGELOG.md | 4 ++++ .../example/lib/main.dart | 4 +++- .../shared_preferences_linux/pubspec.yaml | 2 +- .../shared_preferences_windows/CHANGELOG.md | 4 ++++ .../example/lib/main.dart | 4 +++- .../shared_preferences_windows/pubspec.yaml | 2 +- .../url_launcher/url_launcher/CHANGELOG.md | 4 ++++ packages/url_launcher/url_launcher/README.md | 6 +++--- .../url_launcher/example/lib/basic.dart | 2 +- .../url_launcher/example/lib/files.dart | 4 ++-- .../url_launcher/example/lib/main.dart | 8 ++++---- .../url_launcher/url_launcher/pubspec.yaml | 2 +- .../url_launcher_android/CHANGELOG.md | 4 ++++ .../example/lib/main.dart | 8 ++++---- .../url_launcher_android/pubspec.yaml | 2 +- .../url_launcher_ios/CHANGELOG.md | 3 ++- .../url_launcher_ios/example/lib/main.dart | 10 +++++----- .../url_launcher_ios/pubspec.yaml | 2 +- .../url_launcher_linux/CHANGELOG.md | 3 ++- .../url_launcher_linux/example/lib/main.dart | 2 +- .../url_launcher_linux/pubspec.yaml | 2 +- .../url_launcher_macos/CHANGELOG.md | 3 ++- .../url_launcher_macos/example/lib/main.dart | 2 +- .../url_launcher_macos/pubspec.yaml | 2 +- .../url_launcher_web/CHANGELOG.md | 3 ++- .../url_launcher_web/lib/src/link.dart | 8 ++++++-- .../url_launcher_web/pubspec.yaml | 2 +- .../url_launcher_windows/CHANGELOG.md | 3 ++- .../example/lib/main.dart | 2 +- .../url_launcher_windows/pubspec.yaml | 2 +- .../video_player/video_player/CHANGELOG.md | 4 ++++ .../video_player/lib/video_player.dart | 18 +++++++----------- .../video_player/video_player/pubspec.yaml | 2 +- .../webview_flutter/CHANGELOG.md | 4 ++++ .../lib/src/legacy/webview.dart | 1 + .../webview_flutter/pubspec.yaml | 2 +- .../webview_flutter_android/CHANGELOG.md | 4 ++++ .../lib/src/android_webview_controller.dart | 14 ++++++++++---- .../src/legacy/webview_android_widget.dart | 14 ++++++++++---- .../webview_flutter_android/pubspec.yaml | 2 +- 89 files changed, 281 insertions(+), 128 deletions(-) diff --git a/analysis_options.yaml b/analysis_options.yaml index 85f5bde1b0de..27f59e1bd5bd 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -141,19 +141,19 @@ linter: # - literal_only_boolean_expressions # too many false positives: https://github.com/dart-lang/linter/issues/453 - missing_whitespace_between_adjacent_strings - no_adjacent_strings_in_list - # - no_default_cases # LOCAL CHANGE - Needs to be enabled and violations fixed. + - no_default_cases - no_duplicate_case_values - no_leading_underscores_for_library_prefixes - no_leading_underscores_for_local_identifiers - no_logic_in_create_state - # - no_runtimeType_toString # ok in tests; we enable this only in packages/ + - no_runtimeType_toString # DIFFERENT FROM FLUTTER/FLUTTER - non_constant_identifier_names - noop_primitive_operations - null_check_on_nullable_type_parameter - null_closures # - omit_local_variable_types # opposite of always_specify_types # - one_member_abstracts # too many false positives - # - only_throw_errors # this does get disabled in a few places where we have legacy code that uses strings et al # LOCAL CHANGE - Needs to be enabled and violations fixed. + - only_throw_errors # this does get disabled in a few places where we have legacy code that uses strings et al - overridden_fields - package_api_docs - package_names @@ -200,7 +200,7 @@ linter: - prefer_typing_uninitialized_variables - prefer_void_to_null - provide_deprecation_message - # - public_member_api_docs # enabled on a case-by-case basis; see e.g. packages/analysis_options.yaml + - public_member_api_docs # DIFFERENT FROM FLUTTER/FLUTTER - recursive_getters # - require_trailing_commas # blocked on https://github.com/dart-lang/sdk/issues/47441 - secure_pubspec_urls @@ -261,8 +261,3 @@ linter: # - use_to_and_as_if_applicable # has false positives, so we prefer to catch this by code-review - valid_regexps - void_checks - ### Local flutter/plugins additions ### - # These are from flutter/flutter/packages, so will need to be preserved - # separately when moving to a shared file. - - no_runtimeType_toString # use objectRuntimeType from package:foundation - - public_member_api_docs # see https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#documentation-dartdocs-javadocs-etc diff --git a/packages/camera/camera/CHANGELOG.md b/packages/camera/camera/CHANGELOG.md index d19a528a769e..f7db10b19c99 100644 --- a/packages/camera/camera/CHANGELOG.md +++ b/packages/camera/camera/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.10.2+1 + +* Updates code for stricter lint checks. + ## 0.10.2 * Implements option to also stream when recording a video. @@ -31,7 +35,7 @@ ## 0.10.0 * **Breaking Change** Bumps default camera_web package version, which updates permission exception code from `cameraPermission` to `CameraAccessDenied`. -* **Breaking Change** Bumps default camera_android package version, which updates permission exception code from `cameraPermission` to +* **Breaking Change** Bumps default camera_android package version, which updates permission exception code from `cameraPermission` to `CameraAccessDenied` and `AudioAccessDenied`. * Ignores unnecessary import warnings in preparation for [upcoming Flutter changes](https://github.com/flutter/flutter/pull/106316). diff --git a/packages/camera/camera/example/lib/main.dart b/packages/camera/camera/example/lib/main.dart index 4911625ae08e..b343b6da9d89 100644 --- a/packages/camera/camera/example/lib/main.dart +++ b/packages/camera/camera/example/lib/main.dart @@ -31,9 +31,11 @@ IconData getCameraLensIcon(CameraLensDirection direction) { return Icons.camera_front; case CameraLensDirection.external: return Icons.camera; - default: - throw ArgumentError('Unknown lens direction'); } + // This enum is from a different package, so a new value could be added at + // any time. The example should keep working if that happens. + // ignore: dead_code + return Icons.camera; } void _logError(String code, String? message) { diff --git a/packages/camera/camera/pubspec.yaml b/packages/camera/camera/pubspec.yaml index f8b23bf09db8..fb62665e2e39 100644 --- a/packages/camera/camera/pubspec.yaml +++ b/packages/camera/camera/pubspec.yaml @@ -4,7 +4,7 @@ description: A Flutter plugin for controlling the camera. Supports previewing Dart. repository: https://github.com/flutter/plugins/tree/main/packages/camera/camera issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22 -version: 0.10.2 +version: 0.10.2+1 environment: sdk: ">=2.14.0 <3.0.0" diff --git a/packages/camera/camera_android/CHANGELOG.md b/packages/camera/camera_android/CHANGELOG.md index dda28f0c96b4..7174cf4383a5 100644 --- a/packages/camera/camera_android/CHANGELOG.md +++ b/packages/camera/camera_android/CHANGELOG.md @@ -1,7 +1,11 @@ +## 0.10.2+3 + +* Updates code for stricter lint checks. + ## 0.10.2+2 * Fixes zoom computation for virtual cameras hiding physical cameras in Android 11+. -* Removes the unused CameraZoom class from the codebase. +* Removes the unused CameraZoom class from the codebase. ## 0.10.2+1 diff --git a/packages/camera/camera_android/example/lib/main.dart b/packages/camera/camera_android/example/lib/main.dart index af9aab1a8a86..a66d6e168aff 100644 --- a/packages/camera/camera_android/example/lib/main.dart +++ b/packages/camera/camera_android/example/lib/main.dart @@ -35,9 +35,11 @@ IconData getCameraLensIcon(CameraLensDirection direction) { return Icons.camera_front; case CameraLensDirection.external: return Icons.camera; - default: - throw ArgumentError('Unknown lens direction'); } + // This enum is from a different package, so a new value could be added at + // any time. The example should keep working if that happens. + // ignore: dead_code + return Icons.camera; } void _logError(String code, String? message) { diff --git a/packages/camera/camera_android/lib/src/android_camera.dart b/packages/camera/camera_android/lib/src/android_camera.dart index 1a9c3d0e1bdd..9ab9b578616a 100644 --- a/packages/camera/camera_android/lib/src/android_camera.dart +++ b/packages/camera/camera_android/lib/src/android_camera.dart @@ -145,6 +145,7 @@ class AndroidCamera extends CameraPlatform { // ignore: body_might_complete_normally_catch_error (Object error, StackTrace stackTrace) { if (error is! PlatformException) { + // ignore: only_throw_errors throw error; } completer.completeError( @@ -520,9 +521,14 @@ class AndroidCamera extends CameraPlatform { return 'always'; case FlashMode.torch: return 'torch'; - default: - throw ArgumentError('Unknown FlashMode value'); } + // The enum comes from a different package, which could get a new value at + // any time, so provide a fallback that ensures this won't break when used + // with a version that contains new values. This is deliberately outside + // the switch rather than a `default` so that the linter will flag the + // switch as needing an update. + // ignore: dead_code + return 'off'; } /// Returns the resolution preset as a String. @@ -540,9 +546,14 @@ class AndroidCamera extends CameraPlatform { return 'medium'; case ResolutionPreset.low: return 'low'; - default: - throw ArgumentError('Unknown ResolutionPreset value'); } + // The enum comes from a different package, which could get a new value at + // any time, so provide a fallback that ensures this won't break when used + // with a version that contains new values. This is deliberately outside + // the switch rather than a `default` so that the linter will flag the + // switch as needing an update. + // ignore: dead_code + return 'max'; } /// Converts messages received from the native platform into device events. diff --git a/packages/camera/camera_android/lib/src/utils.dart b/packages/camera/camera_android/lib/src/utils.dart index 663ec6da7a97..8d58f7fe1297 100644 --- a/packages/camera/camera_android/lib/src/utils.dart +++ b/packages/camera/camera_android/lib/src/utils.dart @@ -29,9 +29,14 @@ String serializeDeviceOrientation(DeviceOrientation orientation) { return 'landscapeRight'; case DeviceOrientation.landscapeLeft: return 'landscapeLeft'; - default: - throw ArgumentError('Unknown DeviceOrientation value'); } + // The enum comes from a different package, which could get a new value at + // any time, so provide a fallback that ensures this won't break when used + // with a version that contains new values. This is deliberately outside + // the switch rather than a `default` so that the linter will flag the + // switch as needing an update. + // ignore: dead_code + return 'portraitUp'; } /// Returns the device orientation for a given String. diff --git a/packages/camera/camera_android/pubspec.yaml b/packages/camera/camera_android/pubspec.yaml index 20a86ad36c19..d61642faa387 100644 --- a/packages/camera/camera_android/pubspec.yaml +++ b/packages/camera/camera_android/pubspec.yaml @@ -2,7 +2,7 @@ name: camera_android description: Android implementation of the camera plugin. repository: https://github.com/flutter/plugins/tree/main/packages/camera/camera_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22 -version: 0.10.2+2 +version: 0.10.2+3 environment: sdk: ">=2.14.0 <3.0.0" diff --git a/packages/camera/camera_avfoundation/CHANGELOG.md b/packages/camera/camera_avfoundation/CHANGELOG.md index d9fc8c37eb4f..6cc6b1bd005c 100644 --- a/packages/camera/camera_avfoundation/CHANGELOG.md +++ b/packages/camera/camera_avfoundation/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.9.10+2 + +* Updates code for stricter lint checks. + ## 0.9.10+1 * Updates code for stricter lint checks. diff --git a/packages/camera/camera_avfoundation/example/lib/main.dart b/packages/camera/camera_avfoundation/example/lib/main.dart index af9aab1a8a86..a66d6e168aff 100644 --- a/packages/camera/camera_avfoundation/example/lib/main.dart +++ b/packages/camera/camera_avfoundation/example/lib/main.dart @@ -35,9 +35,11 @@ IconData getCameraLensIcon(CameraLensDirection direction) { return Icons.camera_front; case CameraLensDirection.external: return Icons.camera; - default: - throw ArgumentError('Unknown lens direction'); } + // This enum is from a different package, so a new value could be added at + // any time. The example should keep working if that happens. + // ignore: dead_code + return Icons.camera; } void _logError(String code, String? message) { diff --git a/packages/camera/camera_avfoundation/lib/src/avfoundation_camera.dart b/packages/camera/camera_avfoundation/lib/src/avfoundation_camera.dart index 11bda5155819..5080c57a736f 100644 --- a/packages/camera/camera_avfoundation/lib/src/avfoundation_camera.dart +++ b/packages/camera/camera_avfoundation/lib/src/avfoundation_camera.dart @@ -145,6 +145,7 @@ class AVFoundationCamera extends CameraPlatform { // ignore: body_might_complete_normally_catch_error (Object error, StackTrace stackTrace) { if (error is! PlatformException) { + // ignore: only_throw_errors throw error; } completer.completeError( @@ -526,9 +527,14 @@ class AVFoundationCamera extends CameraPlatform { return 'always'; case FlashMode.torch: return 'torch'; - default: - throw ArgumentError('Unknown FlashMode value'); } + // The enum comes from a different package, which could get a new value at + // any time, so provide a fallback that ensures this won't break when used + // with a version that contains new values. This is deliberately outside + // the switch rather than a `default` so that the linter will flag the + // switch as needing an update. + // ignore: dead_code + return 'off'; } /// Returns the resolution preset as a String. @@ -546,9 +552,14 @@ class AVFoundationCamera extends CameraPlatform { return 'medium'; case ResolutionPreset.low: return 'low'; - default: - throw ArgumentError('Unknown ResolutionPreset value'); } + // The enum comes from a different package, which could get a new value at + // any time, so provide a fallback that ensures this won't break when used + // with a version that contains new values. This is deliberately outside + // the switch rather than a `default` so that the linter will flag the + // switch as needing an update. + // ignore: dead_code + return 'max'; } /// Converts messages received from the native platform into device events. diff --git a/packages/camera/camera_avfoundation/lib/src/utils.dart b/packages/camera/camera_avfoundation/lib/src/utils.dart index 663ec6da7a97..8d58f7fe1297 100644 --- a/packages/camera/camera_avfoundation/lib/src/utils.dart +++ b/packages/camera/camera_avfoundation/lib/src/utils.dart @@ -29,9 +29,14 @@ String serializeDeviceOrientation(DeviceOrientation orientation) { return 'landscapeRight'; case DeviceOrientation.landscapeLeft: return 'landscapeLeft'; - default: - throw ArgumentError('Unknown DeviceOrientation value'); } + // The enum comes from a different package, which could get a new value at + // any time, so provide a fallback that ensures this won't break when used + // with a version that contains new values. This is deliberately outside + // the switch rather than a `default` so that the linter will flag the + // switch as needing an update. + // ignore: dead_code + return 'portraitUp'; } /// Returns the device orientation for a given String. diff --git a/packages/camera/camera_avfoundation/pubspec.yaml b/packages/camera/camera_avfoundation/pubspec.yaml index 975accff84b9..94a700240308 100644 --- a/packages/camera/camera_avfoundation/pubspec.yaml +++ b/packages/camera/camera_avfoundation/pubspec.yaml @@ -2,7 +2,7 @@ name: camera_avfoundation description: iOS implementation of the camera plugin. repository: https://github.com/flutter/plugins/tree/main/packages/camera/camera_avfoundation issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22 -version: 0.9.10+1 +version: 0.9.10+2 environment: sdk: ">=2.14.0 <3.0.0" diff --git a/packages/camera/camera_platform_interface/CHANGELOG.md b/packages/camera/camera_platform_interface/CHANGELOG.md index 55d93a3057bd..ba27eb3c7fe6 100644 --- a/packages/camera/camera_platform_interface/CHANGELOG.md +++ b/packages/camera/camera_platform_interface/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.3.4 + +* Updates code for stricter lint checks. + ## 2.3.3 * Updates code for stricter lint checks. diff --git a/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart b/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart index 06c1ac69570f..f770499c755a 100644 --- a/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart +++ b/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart @@ -137,6 +137,7 @@ class MethodChannelCamera extends CameraPlatform { // ignore: body_might_complete_normally_catch_error (Object error, StackTrace stackTrace) { if (error is! PlatformException) { + // ignore: only_throw_errors throw error; } completer.completeError( @@ -519,8 +520,6 @@ class MethodChannelCamera extends CameraPlatform { return 'always'; case FlashMode.torch: return 'torch'; - default: - throw ArgumentError('Unknown FlashMode value'); } } @@ -539,8 +538,6 @@ class MethodChannelCamera extends CameraPlatform { return 'medium'; case ResolutionPreset.low: return 'low'; - default: - throw ArgumentError('Unknown ResolutionPreset value'); } } diff --git a/packages/camera/camera_platform_interface/lib/src/types/exposure_mode.dart b/packages/camera/camera_platform_interface/lib/src/types/exposure_mode.dart index 56a05cd2d0f1..6da44c98ddc8 100644 --- a/packages/camera/camera_platform_interface/lib/src/types/exposure_mode.dart +++ b/packages/camera/camera_platform_interface/lib/src/types/exposure_mode.dart @@ -18,8 +18,6 @@ String serializeExposureMode(ExposureMode exposureMode) { return 'locked'; case ExposureMode.auto: return 'auto'; - default: - throw ArgumentError('Unknown ExposureMode value'); } } diff --git a/packages/camera/camera_platform_interface/lib/src/types/focus_mode.dart b/packages/camera/camera_platform_interface/lib/src/types/focus_mode.dart index 6baae0c1f63e..1f9cbef1bab9 100644 --- a/packages/camera/camera_platform_interface/lib/src/types/focus_mode.dart +++ b/packages/camera/camera_platform_interface/lib/src/types/focus_mode.dart @@ -18,8 +18,6 @@ String serializeFocusMode(FocusMode focusMode) { return 'locked'; case FocusMode.auto: return 'auto'; - default: - throw ArgumentError('Unknown FocusMode value'); } } diff --git a/packages/camera/camera_platform_interface/lib/src/types/image_format_group.dart b/packages/camera/camera_platform_interface/lib/src/types/image_format_group.dart index edbf7d24098c..8dc69e09f58a 100644 --- a/packages/camera/camera_platform_interface/lib/src/types/image_format_group.dart +++ b/packages/camera/camera_platform_interface/lib/src/types/image_format_group.dart @@ -47,7 +47,6 @@ extension ImageFormatGroupName on ImageFormatGroup { case ImageFormatGroup.jpeg: return 'jpeg'; case ImageFormatGroup.unknown: - default: return 'unknown'; } } diff --git a/packages/camera/camera_platform_interface/lib/src/utils/utils.dart b/packages/camera/camera_platform_interface/lib/src/utils/utils.dart index d86880afd216..771a94be416e 100644 --- a/packages/camera/camera_platform_interface/lib/src/utils/utils.dart +++ b/packages/camera/camera_platform_interface/lib/src/utils/utils.dart @@ -30,8 +30,6 @@ String serializeDeviceOrientation(DeviceOrientation orientation) { return 'landscapeRight'; case DeviceOrientation.landscapeLeft: return 'landscapeLeft'; - default: - throw ArgumentError('Unknown DeviceOrientation value'); } } diff --git a/packages/camera/camera_platform_interface/pubspec.yaml b/packages/camera/camera_platform_interface/pubspec.yaml index 54383cd853ee..50ce9bee8531 100644 --- a/packages/camera/camera_platform_interface/pubspec.yaml +++ b/packages/camera/camera_platform_interface/pubspec.yaml @@ -4,7 +4,7 @@ repository: https://github.com/flutter/plugins/tree/main/packages/camera/camera_ issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22 # 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: 2.3.3 +version: 2.3.4 environment: sdk: '>=2.12.0 <3.0.0' diff --git a/packages/camera/camera_web/CHANGELOG.md b/packages/camera/camera_web/CHANGELOG.md index d8d0c93dde11..eb19a54b6555 100644 --- a/packages/camera/camera_web/CHANGELOG.md +++ b/packages/camera/camera_web/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.3.1+1 + +* Updates code for stricter lint checks. + ## 0.3.1 * Updates to latest camera platform interface, and fails if user attempts to use streaming with recording (since streaming is currently unsupported on web). diff --git a/packages/camera/camera_web/lib/src/camera_service.dart b/packages/camera/camera_web/lib/src/camera_service.dart index 6e20c7d74f78..451278c23fc3 100644 --- a/packages/camera/camera_web/lib/src/camera_service.dart +++ b/packages/camera/camera_web/lib/src/camera_service.dart @@ -299,9 +299,15 @@ class CameraService { case ResolutionPreset.medium: return const Size(720, 480); case ResolutionPreset.low: - default: return const Size(320, 240); } + // The enum comes from a different package, which could get a new value at + // any time, so provide a fallback that ensures this won't break when used + // with a version that contains new values. This is deliberately outside + // the switch rather than a `default` so that the linter will flag the + // switch as needing an update. + // ignore: dead_code + return const Size(320, 240); } /// Maps the given [deviceOrientation] to [OrientationType]. diff --git a/packages/camera/camera_web/pubspec.yaml b/packages/camera/camera_web/pubspec.yaml index 2368e62abee6..b5b19fc487ac 100644 --- a/packages/camera/camera_web/pubspec.yaml +++ b/packages/camera/camera_web/pubspec.yaml @@ -2,7 +2,7 @@ name: camera_web description: A Flutter plugin for getting information about and controlling the camera on Web. repository: https://github.com/flutter/plugins/tree/main/packages/camera/camera_web issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22 -version: 0.3.1 +version: 0.3.1+1 environment: sdk: ">=2.12.0 <3.0.0" diff --git a/packages/google_maps_flutter/google_maps_flutter_android/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter_android/CHANGELOG.md index 66c5fcf85738..8264c559e677 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/CHANGELOG.md +++ b/packages/google_maps_flutter/google_maps_flutter_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.4.3 + +* Updates code for stricter lint checks. + ## 2.4.2 * Updates code for stricter lint checks. diff --git a/packages/google_maps_flutter/google_maps_flutter_android/lib/src/google_maps_flutter_android.dart b/packages/google_maps_flutter/google_maps_flutter_android/lib/src/google_maps_flutter_android.dart index 0188bb18fb20..0461b4cf71bc 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/lib/src/google_maps_flutter_android.dart +++ b/packages/google_maps_flutter/google_maps_flutter_android/lib/src/google_maps_flutter_android.dart @@ -539,7 +539,7 @@ class GoogleMapsFlutterAndroid extends GoogleMapsFlutterPlatform { preferredRenderer = 'legacy'; break; case AndroidMapRenderer.platformDefault: - default: + case null: preferredRenderer = 'default'; } diff --git a/packages/google_maps_flutter/google_maps_flutter_android/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_android/pubspec.yaml index 492193e39f86..d67e85f15e9a 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter_android/pubspec.yaml @@ -2,7 +2,7 @@ name: google_maps_flutter_android description: Android implementation of the google_maps_flutter plugin. repository: https://github.com/flutter/plugins/tree/main/packages/google_maps_flutter/google_maps_flutter_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+maps%22 -version: 2.4.2 +version: 2.4.3 environment: sdk: ">=2.14.0 <3.0.0" diff --git a/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md index 3bbc08ac7186..3e3ee7f65a04 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md +++ b/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.4.0+5 + +* Updates code for stricter lint checks. + ## 0.4.0+4 * Updates code for stricter lint checks. diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/convert.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/convert.dart index d5f27ee05d0c..25cba849475b 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/convert.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/convert.dart @@ -98,9 +98,15 @@ gmaps.MapTypeId _gmapTypeIDForPluginType(MapType type) { return gmaps.MapTypeId.HYBRID; case MapType.normal: case MapType.none: - default: return gmaps.MapTypeId.ROADMAP; } + // The enum comes from a different package, which could get a new value at + // any time, so provide a fallback that ensures this won't break when used + // with a version that contains new values. This is deliberately outside + // the switch rather than a `default` so that the linter will flag the + // switch as needing an update. + // ignore: dead_code + return gmaps.MapTypeId.ROADMAP; } gmaps.MapOptions _applyInitialPosition( diff --git a/packages/google_maps_flutter/google_maps_flutter_web/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_web/pubspec.yaml index 6278ab01ba6c..e4d02b11eead 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter_web/pubspec.yaml @@ -2,7 +2,7 @@ name: google_maps_flutter_web description: Web platform implementation of google_maps_flutter repository: https://github.com/flutter/plugins/tree/main/packages/google_maps_flutter/google_maps_flutter_web issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+maps%22 -version: 0.4.0+4 +version: 0.4.0+5 environment: sdk: ">=2.12.0 <3.0.0" diff --git a/packages/image_picker/image_picker/CHANGELOG.md b/packages/image_picker/image_picker/CHANGELOG.md index 76192566b18b..2974cfe4d26f 100644 --- a/packages/image_picker/image_picker/CHANGELOG.md +++ b/packages/image_picker/image_picker/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.8.6+1 + +* Updates code for stricter lint checks. + ## 0.8.6 * Updates minimum Flutter version to 2.10. diff --git a/packages/image_picker/image_picker/example/lib/main.dart b/packages/image_picker/image_picker/example/lib/main.dart index 5e448ddbee68..f4f6546b1a98 100755 --- a/packages/image_picker/image_picker/example/lib/main.dart +++ b/packages/image_picker/image_picker/example/lib/main.dart @@ -260,7 +260,7 @@ class _MyHomePageState extends State { ); case ConnectionState.done: return _handlePreview(); - default: + case ConnectionState.active: if (snapshot.hasError) { return Text( 'Pick image/video error: ${snapshot.error}}', diff --git a/packages/image_picker/image_picker/pubspec.yaml b/packages/image_picker/image_picker/pubspec.yaml index 7fed3bf4637b..c2bf82278fa4 100755 --- a/packages/image_picker/image_picker/pubspec.yaml +++ b/packages/image_picker/image_picker/pubspec.yaml @@ -3,7 +3,7 @@ description: Flutter plugin for selecting images from the Android and iOS image library, and taking new pictures with the camera. repository: https://github.com/flutter/plugins/tree/main/packages/image_picker/image_picker issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+image_picker%22 -version: 0.8.6 +version: 0.8.6+1 environment: sdk: ">=2.14.0 <3.0.0" diff --git a/packages/image_picker/image_picker_android/CHANGELOG.md b/packages/image_picker/image_picker_android/CHANGELOG.md index b041761181d0..06d35d2f1a67 100644 --- a/packages/image_picker/image_picker_android/CHANGELOG.md +++ b/packages/image_picker/image_picker_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.8.5+5 + +* Updates code for stricter lint checks. + ## 0.8.5+4 * Fixes null cast exception when restoring a cancelled selection. diff --git a/packages/image_picker/image_picker_android/example/lib/main.dart b/packages/image_picker/image_picker_android/example/lib/main.dart index 212e064cc6e5..d7c82a3a9979 100755 --- a/packages/image_picker/image_picker_android/example/lib/main.dart +++ b/packages/image_picker/image_picker_android/example/lib/main.dart @@ -260,7 +260,7 @@ class _MyHomePageState extends State { ); case ConnectionState.done: return _handlePreview(); - default: + case ConnectionState.active: if (snapshot.hasError) { return Text( 'Pick image/video error: ${snapshot.error}}', diff --git a/packages/image_picker/image_picker_android/pubspec.yaml b/packages/image_picker/image_picker_android/pubspec.yaml index 4c32e345007c..461ddfc1c437 100755 --- a/packages/image_picker/image_picker_android/pubspec.yaml +++ b/packages/image_picker/image_picker_android/pubspec.yaml @@ -2,7 +2,7 @@ name: image_picker_android description: Android implementation of the image_picker plugin. repository: https://github.com/flutter/plugins/tree/main/packages/image_picker/image_picker_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+image_picker%22 -version: 0.8.5+4 +version: 0.8.5+5 environment: sdk: ">=2.14.0 <3.0.0" diff --git a/packages/image_picker/image_picker_ios/CHANGELOG.md b/packages/image_picker/image_picker_ios/CHANGELOG.md index 9720dba0a305..d691b3201e69 100644 --- a/packages/image_picker/image_picker_ios/CHANGELOG.md +++ b/packages/image_picker/image_picker_ios/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.8.6+6 + +* Updates code for stricter lint checks. + ## 0.8.6+5 * Fixes crash when `imageQuality` is set. diff --git a/packages/image_picker/image_picker_ios/lib/image_picker_ios.dart b/packages/image_picker/image_picker_ios/lib/image_picker_ios.dart index fbc356f212b8..3f76784ff07c 100644 --- a/packages/image_picker/image_picker_ios/lib/image_picker_ios.dart +++ b/packages/image_picker/image_picker_ios/lib/image_picker_ios.dart @@ -14,9 +14,14 @@ SourceType _convertSource(ImageSource source) { return SourceType.camera; case ImageSource.gallery: return SourceType.gallery; - default: - throw UnimplementedError('Unknown source: $source'); } + // The enum comes from a different package, which could get a new value at + // any time, so a fallback case is necessary. Since there is no reasonable + // default behavior, throw to alert the client that they need an updated + // version. This is deliberately outside the switch rather than a `default` + // so that the linter will flag the switch as needing an update. + // ignore: dead_code + throw UnimplementedError('Unknown source: $source'); } // Converts a [CameraDevice] to the corresponding Pigeon API enum value. @@ -26,9 +31,14 @@ SourceCamera _convertCamera(CameraDevice camera) { return SourceCamera.front; case CameraDevice.rear: return SourceCamera.rear; - default: - throw UnimplementedError('Unknown camera: $camera'); } + // The enum comes from a different package, which could get a new value at + // any time, so a fallback case is necessary. Since there is no reasonable + // default behavior, throw to alert the client that they need an updated + // version. This is deliberately outside the switch rather than a `default` + // so that the linter will flag the switch as needing an update. + // ignore: dead_code + throw UnimplementedError('Unknown camera: $camera'); } /// An implementation of [ImagePickerPlatform] for iOS. diff --git a/packages/image_picker/image_picker_ios/pubspec.yaml b/packages/image_picker/image_picker_ios/pubspec.yaml index 91d96c3d0a20..cfc22c89f18e 100755 --- a/packages/image_picker/image_picker_ios/pubspec.yaml +++ b/packages/image_picker/image_picker_ios/pubspec.yaml @@ -2,7 +2,7 @@ name: image_picker_ios description: iOS implementation of the image_picker plugin. repository: https://github.com/flutter/plugins/tree/main/packages/image_picker/image_picker_ios issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+image_picker%22 -version: 0.8.6+5 +version: 0.8.6+6 environment: sdk: ">=2.14.0 <3.0.0" diff --git a/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_methodchannel_apis_test.dart b/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_methodchannel_apis_test.dart index ff059ffbb1fc..4936f62a911a 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_methodchannel_apis_test.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_methodchannel_apis_test.dart @@ -234,7 +234,7 @@ class FakeStoreKitPlatform { // receipt manager case '-[InAppPurchasePlugin retrieveReceiptData:result:]': if (getReceiptFailTest) { - throw 'some arbitrary error'; + throw Exception('some arbitrary error'); } return Future.value('receipt data'); // payment queue diff --git a/packages/shared_preferences/shared_preferences/CHANGELOG.md b/packages/shared_preferences/shared_preferences/CHANGELOG.md index 6fe9568afa79..db0d6003b7fb 100644 --- a/packages/shared_preferences/shared_preferences/CHANGELOG.md +++ b/packages/shared_preferences/shared_preferences/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.0.17 + +* Updates code for stricter lint checks. + ## 2.0.16 * Switches to the new `shared_preferences_foundation` implementation package diff --git a/packages/shared_preferences/shared_preferences/example/lib/main.dart b/packages/shared_preferences/shared_preferences/example/lib/main.dart index a2e72b446925..f9690395f10d 100644 --- a/packages/shared_preferences/shared_preferences/example/lib/main.dart +++ b/packages/shared_preferences/shared_preferences/example/lib/main.dart @@ -66,9 +66,11 @@ class SharedPreferencesDemoState extends State { future: _counter, builder: (BuildContext context, AsyncSnapshot snapshot) { switch (snapshot.connectionState) { + case ConnectionState.none: case ConnectionState.waiting: return const CircularProgressIndicator(); - default: + case ConnectionState.active: + case ConnectionState.done: if (snapshot.hasError) { return Text('Error: ${snapshot.error}'); } else { diff --git a/packages/shared_preferences/shared_preferences/pubspec.yaml b/packages/shared_preferences/shared_preferences/pubspec.yaml index cfa7f005e970..aab200f252fb 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. repository: https://github.com/flutter/plugins/tree/main/packages/shared_preferences/shared_preferences issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+shared_preferences%22 -version: 2.0.16 +version: 2.0.17 environment: sdk: ">=2.14.0 <3.0.0" diff --git a/packages/shared_preferences/shared_preferences_android/CHANGELOG.md b/packages/shared_preferences/shared_preferences_android/CHANGELOG.md index d9d7bbd82f46..1e348a124702 100644 --- a/packages/shared_preferences/shared_preferences_android/CHANGELOG.md +++ b/packages/shared_preferences/shared_preferences_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.0.15 + +* Updates code for stricter lint checks. + ## 2.0.14 * Fixes typo in `SharedPreferencesAndroid` docs. diff --git a/packages/shared_preferences/shared_preferences_android/example/lib/main.dart b/packages/shared_preferences/shared_preferences_android/example/lib/main.dart index bb513b09f6d5..cbcad6391beb 100644 --- a/packages/shared_preferences/shared_preferences_android/example/lib/main.dart +++ b/packages/shared_preferences/shared_preferences_android/example/lib/main.dart @@ -70,9 +70,11 @@ class SharedPreferencesDemoState extends State { future: _counter, builder: (BuildContext context, AsyncSnapshot snapshot) { switch (snapshot.connectionState) { + case ConnectionState.none: case ConnectionState.waiting: return const CircularProgressIndicator(); - default: + case ConnectionState.active: + case ConnectionState.done: if (snapshot.hasError) { return Text('Error: ${snapshot.error}'); } else { diff --git a/packages/shared_preferences/shared_preferences_android/pubspec.yaml b/packages/shared_preferences/shared_preferences_android/pubspec.yaml index 7692c114bfce..589c5b2f15fd 100644 --- a/packages/shared_preferences/shared_preferences_android/pubspec.yaml +++ b/packages/shared_preferences/shared_preferences_android/pubspec.yaml @@ -2,7 +2,7 @@ name: shared_preferences_android description: Android implementation of the shared_preferences plugin repository: https://github.com/flutter/plugins/tree/main/packages/shared_preferences/shared_preferences_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+shared_preferences%22 -version: 2.0.14 +version: 2.0.15 environment: sdk: ">=2.14.0 <3.0.0" diff --git a/packages/shared_preferences/shared_preferences_foundation/CHANGELOG.md b/packages/shared_preferences/shared_preferences_foundation/CHANGELOG.md index 0a9baa072f53..788b841c97f9 100644 --- a/packages/shared_preferences/shared_preferences_foundation/CHANGELOG.md +++ b/packages/shared_preferences/shared_preferences_foundation/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.1.2 + +* Updates code for stricter lint checks. + ## 2.1.1 * Adds Swift runtime search paths in podspec to avoid crash in Objective-C apps. diff --git a/packages/shared_preferences/shared_preferences_foundation/example/lib/main.dart b/packages/shared_preferences/shared_preferences_foundation/example/lib/main.dart index e6bbe5931471..a5aedd54ab6f 100644 --- a/packages/shared_preferences/shared_preferences_foundation/example/lib/main.dart +++ b/packages/shared_preferences/shared_preferences_foundation/example/lib/main.dart @@ -72,9 +72,11 @@ class SharedPreferencesDemoState extends State { future: _counter, builder: (BuildContext context, AsyncSnapshot snapshot) { switch (snapshot.connectionState) { + case ConnectionState.none: case ConnectionState.waiting: return const CircularProgressIndicator(); - default: + case ConnectionState.active: + case ConnectionState.done: if (snapshot.hasError) { return Text('Error: ${snapshot.error}'); } else { diff --git a/packages/shared_preferences/shared_preferences_foundation/pubspec.yaml b/packages/shared_preferences/shared_preferences_foundation/pubspec.yaml index 8e65d1bda9b6..6641a3455fb5 100644 --- a/packages/shared_preferences/shared_preferences_foundation/pubspec.yaml +++ b/packages/shared_preferences/shared_preferences_foundation/pubspec.yaml @@ -2,7 +2,7 @@ name: shared_preferences_foundation description: iOS and macOS implementation of the shared_preferences plugin. repository: https://github.com/flutter/plugins/tree/main/packages/shared_preferences/shared_preferences_foundation issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+shared_preferences%22 -version: 2.1.1 +version: 2.1.2 environment: sdk: ">=2.12.0 <3.0.0" diff --git a/packages/shared_preferences/shared_preferences_linux/CHANGELOG.md b/packages/shared_preferences/shared_preferences_linux/CHANGELOG.md index 7c518fcf71cb..21eb16887d13 100644 --- a/packages/shared_preferences/shared_preferences_linux/CHANGELOG.md +++ b/packages/shared_preferences/shared_preferences_linux/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.1.3 + +* Updates code for stricter lint checks. + ## 2.1.2 * Updates code for stricter lint checks. diff --git a/packages/shared_preferences/shared_preferences_linux/example/lib/main.dart b/packages/shared_preferences/shared_preferences_linux/example/lib/main.dart index d51be33baeed..a904c824d4fe 100644 --- a/packages/shared_preferences/shared_preferences_linux/example/lib/main.dart +++ b/packages/shared_preferences/shared_preferences_linux/example/lib/main.dart @@ -66,9 +66,11 @@ class SharedPreferencesDemoState extends State { future: _counter, builder: (BuildContext context, AsyncSnapshot snapshot) { switch (snapshot.connectionState) { + case ConnectionState.none: case ConnectionState.waiting: return const CircularProgressIndicator(); - default: + case ConnectionState.active: + case ConnectionState.done: if (snapshot.hasError) { return Text('Error: ${snapshot.error}'); } else { diff --git a/packages/shared_preferences/shared_preferences_linux/pubspec.yaml b/packages/shared_preferences/shared_preferences_linux/pubspec.yaml index cd2ca8007e3c..a64fc61860b6 100644 --- a/packages/shared_preferences/shared_preferences_linux/pubspec.yaml +++ b/packages/shared_preferences/shared_preferences_linux/pubspec.yaml @@ -2,7 +2,7 @@ name: shared_preferences_linux description: Linux implementation of the shared_preferences plugin repository: https://github.com/flutter/plugins/tree/main/packages/shared_preferences/shared_preferences_linux issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+shared_preferences%22 -version: 2.1.2 +version: 2.1.3 environment: sdk: ">=2.12.0 <3.0.0" diff --git a/packages/shared_preferences/shared_preferences_windows/CHANGELOG.md b/packages/shared_preferences/shared_preferences_windows/CHANGELOG.md index c486a4ce8f9b..dcf99985843e 100644 --- a/packages/shared_preferences/shared_preferences_windows/CHANGELOG.md +++ b/packages/shared_preferences/shared_preferences_windows/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.1.3 + +* Updates code for stricter lint checks. + ## 2.1.2 * Updates code for stricter lint checks. diff --git a/packages/shared_preferences/shared_preferences_windows/example/lib/main.dart b/packages/shared_preferences/shared_preferences_windows/example/lib/main.dart index 74d5e4c68772..e442c4b69ee5 100644 --- a/packages/shared_preferences/shared_preferences_windows/example/lib/main.dart +++ b/packages/shared_preferences/shared_preferences_windows/example/lib/main.dart @@ -66,9 +66,11 @@ class SharedPreferencesDemoState extends State { future: _counter, builder: (BuildContext context, AsyncSnapshot snapshot) { switch (snapshot.connectionState) { + case ConnectionState.none: case ConnectionState.waiting: return const CircularProgressIndicator(); - default: + case ConnectionState.active: + case ConnectionState.done: if (snapshot.hasError) { return Text('Error: ${snapshot.error}'); } else { diff --git a/packages/shared_preferences/shared_preferences_windows/pubspec.yaml b/packages/shared_preferences/shared_preferences_windows/pubspec.yaml index 7f97759e6364..600a520b7bd2 100644 --- a/packages/shared_preferences/shared_preferences_windows/pubspec.yaml +++ b/packages/shared_preferences/shared_preferences_windows/pubspec.yaml @@ -2,7 +2,7 @@ name: shared_preferences_windows description: Windows implementation of shared_preferences repository: https://github.com/flutter/plugins/tree/main/packages/shared_preferences/shared_preferences_windows issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+shared_preferences%22 -version: 2.1.2 +version: 2.1.3 environment: sdk: '>=2.12.0 <3.0.0' diff --git a/packages/url_launcher/url_launcher/CHANGELOG.md b/packages/url_launcher/url_launcher/CHANGELOG.md index 4b365b8d858d..6244bb418a36 100644 --- a/packages/url_launcher/url_launcher/CHANGELOG.md +++ b/packages/url_launcher/url_launcher/CHANGELOG.md @@ -1,3 +1,7 @@ +## 6.1.8 + +* Updates code for stricter lint checks. + ## 6.1.7 * Updates code for new analysis options. diff --git a/packages/url_launcher/url_launcher/README.md b/packages/url_launcher/url_launcher/README.md index e9e4dae476cc..8fbdfb3930e1 100644 --- a/packages/url_launcher/url_launcher/README.md +++ b/packages/url_launcher/url_launcher/README.md @@ -38,7 +38,7 @@ void main() => runApp( Future _launchUrl() async { if (!await launchUrl(_url)) { - throw 'Could not launch $_url'; + throw Exception('Could not launch $_url'); } } ``` @@ -198,10 +198,10 @@ final String filePath = testFile.absolute.path; final Uri uri = Uri.file(filePath); if (!File(uri.toFilePath()).existsSync()) { - throw '$uri does not exist!'; + throw Exception('$uri does not exist!'); } if (!await launchUrl(uri)) { - throw 'Could not launch $uri'; + throw Exception('Could not launch $uri'); } ``` diff --git a/packages/url_launcher/url_launcher/example/lib/basic.dart b/packages/url_launcher/url_launcher/example/lib/basic.dart index 987ca2134318..422e2aae460c 100644 --- a/packages/url_launcher/url_launcher/example/lib/basic.dart +++ b/packages/url_launcher/url_launcher/example/lib/basic.dart @@ -28,7 +28,7 @@ void main() => runApp( Future _launchUrl() async { if (!await launchUrl(_url)) { - throw 'Could not launch $_url'; + throw Exception('Could not launch $_url'); } } // #enddocregion basic-example diff --git a/packages/url_launcher/url_launcher/example/lib/files.dart b/packages/url_launcher/url_launcher/example/lib/files.dart index d48440670406..7f9d20669ee7 100644 --- a/packages/url_launcher/url_launcher/example/lib/files.dart +++ b/packages/url_launcher/url_launcher/example/lib/files.dart @@ -38,10 +38,10 @@ Future _openFile() async { final Uri uri = Uri.file(filePath); if (!File(uri.toFilePath()).existsSync()) { - throw '$uri does not exist!'; + throw Exception('$uri does not exist!'); } if (!await launchUrl(uri)) { - throw 'Could not launch $uri'; + throw Exception('Could not launch $uri'); } // #enddocregion file } diff --git a/packages/url_launcher/url_launcher/example/lib/main.dart b/packages/url_launcher/url_launcher/example/lib/main.dart index a870e18a53de..9b005cf98db0 100644 --- a/packages/url_launcher/url_launcher/example/lib/main.dart +++ b/packages/url_launcher/url_launcher/example/lib/main.dart @@ -58,7 +58,7 @@ class _MyHomePageState extends State { url, mode: LaunchMode.externalApplication, )) { - throw 'Could not launch $url'; + throw Exception('Could not launch $url'); } } @@ -69,7 +69,7 @@ class _MyHomePageState extends State { webViewConfiguration: const WebViewConfiguration( headers: {'my_header_key': 'my_header_value'}), )) { - throw 'Could not launch $url'; + throw Exception('Could not launch $url'); } } @@ -79,7 +79,7 @@ class _MyHomePageState extends State { mode: LaunchMode.inAppWebView, webViewConfiguration: const WebViewConfiguration(enableJavaScript: false), )) { - throw 'Could not launch $url'; + throw Exception('Could not launch $url'); } } @@ -89,7 +89,7 @@ class _MyHomePageState extends State { mode: LaunchMode.inAppWebView, webViewConfiguration: const WebViewConfiguration(enableDomStorage: false), )) { - throw 'Could not launch $url'; + throw Exception('Could not launch $url'); } } diff --git a/packages/url_launcher/url_launcher/pubspec.yaml b/packages/url_launcher/url_launcher/pubspec.yaml index 642b4b51e90c..4f2fae2d6b62 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. Supports web, phone, SMS, and email schemes. repository: https://github.com/flutter/plugins/tree/main/packages/url_launcher/url_launcher issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+url_launcher%22 -version: 6.1.7 +version: 6.1.8 environment: sdk: ">=2.14.0 <3.0.0" diff --git a/packages/url_launcher/url_launcher_android/CHANGELOG.md b/packages/url_launcher/url_launcher_android/CHANGELOG.md index f2f65949e211..1dbc4af784fb 100644 --- a/packages/url_launcher/url_launcher_android/CHANGELOG.md +++ b/packages/url_launcher/url_launcher_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## 6.0.23 + +* Updates code for stricter lint checks. + ## 6.0.22 * Updates code for new analysis options. diff --git a/packages/url_launcher/url_launcher_android/example/lib/main.dart b/packages/url_launcher/url_launcher_android/example/lib/main.dart index 68cf334d6656..7a77c86aef72 100644 --- a/packages/url_launcher/url_launcher_android/example/lib/main.dart +++ b/packages/url_launcher/url_launcher_android/example/lib/main.dart @@ -63,7 +63,7 @@ class _MyHomePageState extends State { universalLinksOnly: false, headers: {}, )) { - throw 'Could not launch $url'; + throw Exception('Could not launch $url'); } } @@ -77,7 +77,7 @@ class _MyHomePageState extends State { universalLinksOnly: false, headers: {'my_header_key': 'my_header_value'}, )) { - throw 'Could not launch $url'; + throw Exception('Could not launch $url'); } } @@ -91,7 +91,7 @@ class _MyHomePageState extends State { universalLinksOnly: false, headers: {}, )) { - throw 'Could not launch $url'; + throw Exception('Could not launch $url'); } } @@ -105,7 +105,7 @@ class _MyHomePageState extends State { universalLinksOnly: false, headers: {}, )) { - throw 'Could not launch $url'; + throw Exception('Could not launch $url'); } } diff --git a/packages/url_launcher/url_launcher_android/pubspec.yaml b/packages/url_launcher/url_launcher_android/pubspec.yaml index d096b0ccc2cc..3d8a3ad70144 100644 --- a/packages/url_launcher/url_launcher_android/pubspec.yaml +++ b/packages/url_launcher/url_launcher_android/pubspec.yaml @@ -2,7 +2,7 @@ name: url_launcher_android description: Android implementation of the url_launcher plugin. repository: https://github.com/flutter/plugins/tree/main/packages/url_launcher/url_launcher_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+url_launcher%22 -version: 6.0.22 +version: 6.0.23 environment: sdk: ">=2.14.0 <3.0.0" diff --git a/packages/url_launcher/url_launcher_ios/CHANGELOG.md b/packages/url_launcher/url_launcher_ios/CHANGELOG.md index cf018da4f59d..7919b16f4623 100644 --- a/packages/url_launcher/url_launcher_ios/CHANGELOG.md +++ b/packages/url_launcher/url_launcher_ios/CHANGELOG.md @@ -1,5 +1,6 @@ -## NEXT +## 6.0.18 +* Updates code for stricter lint checks. * Updates minimum Flutter version to 2.10. ## 6.0.17 diff --git a/packages/url_launcher/url_launcher_ios/example/lib/main.dart b/packages/url_launcher/url_launcher_ios/example/lib/main.dart index 6e8ce7431a66..08a03063a880 100644 --- a/packages/url_launcher/url_launcher_ios/example/lib/main.dart +++ b/packages/url_launcher/url_launcher_ios/example/lib/main.dart @@ -53,7 +53,7 @@ class _MyHomePageState extends State { headers: {'my_header_key': 'my_header_value'}, ); } else { - throw 'Could not launch $url'; + throw Exception('Could not launch $url'); } } @@ -70,7 +70,7 @@ class _MyHomePageState extends State { headers: {'my_header_key': 'my_header_value'}, ); } else { - throw 'Could not launch $url'; + throw Exception('Could not launch $url'); } } @@ -87,7 +87,7 @@ class _MyHomePageState extends State { headers: {}, ); } else { - throw 'Could not launch $url'; + throw Exception('Could not launch $url'); } } @@ -104,7 +104,7 @@ class _MyHomePageState extends State { headers: {}, ); } else { - throw 'Could not launch $url'; + throw Exception('Could not launch $url'); } } @@ -155,7 +155,7 @@ class _MyHomePageState extends State { headers: {}, ); } else { - throw 'Could not launch $url'; + throw Exception('Could not launch $url'); } } diff --git a/packages/url_launcher/url_launcher_ios/pubspec.yaml b/packages/url_launcher/url_launcher_ios/pubspec.yaml index 5e06a80b4cfe..5af4cf3f3666 100644 --- a/packages/url_launcher/url_launcher_ios/pubspec.yaml +++ b/packages/url_launcher/url_launcher_ios/pubspec.yaml @@ -2,7 +2,7 @@ name: url_launcher_ios description: iOS implementation of the url_launcher plugin. repository: https://github.com/flutter/plugins/tree/main/packages/url_launcher/url_launcher_ios issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+url_launcher%22 -version: 6.0.17 +version: 6.0.18 environment: sdk: ">=2.14.0 <3.0.0" diff --git a/packages/url_launcher/url_launcher_linux/CHANGELOG.md b/packages/url_launcher/url_launcher_linux/CHANGELOG.md index 69d8b24ddf6f..24aec1bd9b34 100644 --- a/packages/url_launcher/url_launcher_linux/CHANGELOG.md +++ b/packages/url_launcher/url_launcher_linux/CHANGELOG.md @@ -1,5 +1,6 @@ -## NEXT +## 3.0.2 +* Updates code for stricter lint checks. * Updates minimum Flutter version to 2.10. ## 3.0.1 diff --git a/packages/url_launcher/url_launcher_linux/example/lib/main.dart b/packages/url_launcher/url_launcher_linux/example/lib/main.dart index 0b985e78ac0d..bbe651ea05de 100644 --- a/packages/url_launcher/url_launcher_linux/example/lib/main.dart +++ b/packages/url_launcher/url_launcher_linux/example/lib/main.dart @@ -50,7 +50,7 @@ class _MyHomePageState extends State { headers: {}, ); } else { - throw 'Could not launch $url'; + throw Exception('Could not launch $url'); } } diff --git a/packages/url_launcher/url_launcher_linux/pubspec.yaml b/packages/url_launcher/url_launcher_linux/pubspec.yaml index 99b237506c60..20b7886628c3 100644 --- a/packages/url_launcher/url_launcher_linux/pubspec.yaml +++ b/packages/url_launcher/url_launcher_linux/pubspec.yaml @@ -2,7 +2,7 @@ name: url_launcher_linux description: Linux implementation of the url_launcher plugin. repository: https://github.com/flutter/plugins/tree/main/packages/url_launcher/url_launcher_linux issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+url_launcher%22 -version: 3.0.1 +version: 3.0.2 environment: sdk: ">=2.12.0 <3.0.0" diff --git a/packages/url_launcher/url_launcher_macos/CHANGELOG.md b/packages/url_launcher/url_launcher_macos/CHANGELOG.md index 7386ecced865..d85da2c2c6e3 100644 --- a/packages/url_launcher/url_launcher_macos/CHANGELOG.md +++ b/packages/url_launcher/url_launcher_macos/CHANGELOG.md @@ -1,5 +1,6 @@ -## NEXT +## 3.0.2 +* Updates code for stricter lint checks. * Updates minimum Flutter version to 2.10. ## 3.0.1 diff --git a/packages/url_launcher/url_launcher_macos/example/lib/main.dart b/packages/url_launcher/url_launcher_macos/example/lib/main.dart index 0b985e78ac0d..bbe651ea05de 100644 --- a/packages/url_launcher/url_launcher_macos/example/lib/main.dart +++ b/packages/url_launcher/url_launcher_macos/example/lib/main.dart @@ -50,7 +50,7 @@ class _MyHomePageState extends State { headers: {}, ); } else { - throw 'Could not launch $url'; + throw Exception('Could not launch $url'); } } diff --git a/packages/url_launcher/url_launcher_macos/pubspec.yaml b/packages/url_launcher/url_launcher_macos/pubspec.yaml index eaf210a367b6..82aef73f6ed1 100644 --- a/packages/url_launcher/url_launcher_macos/pubspec.yaml +++ b/packages/url_launcher/url_launcher_macos/pubspec.yaml @@ -2,7 +2,7 @@ name: url_launcher_macos description: macOS implementation of the url_launcher plugin. repository: https://github.com/flutter/plugins/tree/main/packages/url_launcher/url_launcher_macos issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+url_launcher%22 -version: 3.0.1 +version: 3.0.2 environment: sdk: ">=2.12.0 <3.0.0" diff --git a/packages/url_launcher/url_launcher_web/CHANGELOG.md b/packages/url_launcher/url_launcher_web/CHANGELOG.md index 5454338bde51..da22933efe06 100644 --- a/packages/url_launcher/url_launcher_web/CHANGELOG.md +++ b/packages/url_launcher/url_launcher_web/CHANGELOG.md @@ -1,5 +1,6 @@ -## NEXT +## 2.0.14 +* Updates code for stricter lint checks. * Updates minimum Flutter version to 2.10. ## 2.0.13 diff --git a/packages/url_launcher/url_launcher_web/lib/src/link.dart b/packages/url_launcher/url_launcher_web/lib/src/link.dart index 112d07ea7571..78c049c03def 100644 --- a/packages/url_launcher/url_launcher_web/lib/src/link.dart +++ b/packages/url_launcher/url_launcher_web/lib/src/link.dart @@ -247,9 +247,13 @@ class LinkViewController extends PlatformViewController { return '_self'; case LinkTarget.blank: return '_blank'; - default: - throw Exception('Unknown LinkTarget value $target.'); } + // The enum comes from a different package, which could get a new value at + // any time, so provide a fallback that ensures this won't break when used + // with a version that contains new values. This is deliberately outside + // the switch rather than a `default` so that the linter will flag the + // switch as needing an update. + return '_self'; } @override diff --git a/packages/url_launcher/url_launcher_web/pubspec.yaml b/packages/url_launcher/url_launcher_web/pubspec.yaml index 6d4c80689427..cd5cc49ea08e 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 repository: https://github.com/flutter/plugins/tree/main/packages/url_launcher/url_launcher_web issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+url_launcher%22 -version: 2.0.13 +version: 2.0.14 environment: sdk: ">=2.12.0 <3.0.0" diff --git a/packages/url_launcher/url_launcher_windows/CHANGELOG.md b/packages/url_launcher/url_launcher_windows/CHANGELOG.md index a5952feb4978..8fac4e7f72e3 100644 --- a/packages/url_launcher/url_launcher_windows/CHANGELOG.md +++ b/packages/url_launcher/url_launcher_windows/CHANGELOG.md @@ -1,5 +1,6 @@ -## NEXT +## 3.0.2 +* Updates code for stricter lint checks. * Updates minimum Flutter version to 2.10. ## 3.0.1 diff --git a/packages/url_launcher/url_launcher_windows/example/lib/main.dart b/packages/url_launcher/url_launcher_windows/example/lib/main.dart index 0b985e78ac0d..bbe651ea05de 100644 --- a/packages/url_launcher/url_launcher_windows/example/lib/main.dart +++ b/packages/url_launcher/url_launcher_windows/example/lib/main.dart @@ -50,7 +50,7 @@ class _MyHomePageState extends State { headers: {}, ); } else { - throw 'Could not launch $url'; + throw Exception('Could not launch $url'); } } diff --git a/packages/url_launcher/url_launcher_windows/pubspec.yaml b/packages/url_launcher/url_launcher_windows/pubspec.yaml index b35b62e1d82e..6da67ed28d60 100644 --- a/packages/url_launcher/url_launcher_windows/pubspec.yaml +++ b/packages/url_launcher/url_launcher_windows/pubspec.yaml @@ -2,7 +2,7 @@ name: url_launcher_windows description: Windows implementation of the url_launcher plugin. repository: https://github.com/flutter/plugins/tree/main/packages/url_launcher/url_launcher_windows issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+url_launcher%22 -version: 3.0.1 +version: 3.0.2 environment: sdk: ">=2.12.0 <3.0.0" diff --git a/packages/video_player/video_player/CHANGELOG.md b/packages/video_player/video_player/CHANGELOG.md index fdf73487c076..2df6f5dc5542 100644 --- a/packages/video_player/video_player/CHANGELOG.md +++ b/packages/video_player/video_player/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.5.1 + +* Updates code for stricter lint checks. + ## 2.5.0 * Exposes `VideoScrubber` so it can be used to make custom video progress indicators diff --git a/packages/video_player/video_player/lib/video_player.dart b/packages/video_player/video_player/lib/video_player.dart index f27c34210466..3dbdcb543082 100644 --- a/packages/video_player/video_player/lib/video_player.dart +++ b/packages/video_player/video_player/lib/video_player.dart @@ -697,17 +697,13 @@ class _VideoAppLifeCycleObserver extends Object with WidgetsBindingObserver { @override void didChangeAppLifecycleState(AppLifecycleState state) { - switch (state) { - case AppLifecycleState.paused: - _wasPlayingBeforePause = _controller.value.isPlaying; - _controller.pause(); - break; - case AppLifecycleState.resumed: - if (_wasPlayingBeforePause) { - _controller.play(); - } - break; - default: + if (state == AppLifecycleState.paused) { + _wasPlayingBeforePause = _controller.value.isPlaying; + _controller.pause(); + } else if (state == AppLifecycleState.resumed) { + if (_wasPlayingBeforePause) { + _controller.play(); + } } } diff --git a/packages/video_player/video_player/pubspec.yaml b/packages/video_player/video_player/pubspec.yaml index 0f37c0328df2..5757ba9c8016 100644 --- a/packages/video_player/video_player/pubspec.yaml +++ b/packages/video_player/video_player/pubspec.yaml @@ -3,7 +3,7 @@ description: Flutter plugin for displaying inline video with other Flutter widgets on Android, iOS, and web. repository: https://github.com/flutter/plugins/tree/main/packages/video_player/video_player issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+video_player%22 -version: 2.5.0 +version: 2.5.1 environment: sdk: ">=2.14.0 <3.0.0" diff --git a/packages/webview_flutter/webview_flutter/CHANGELOG.md b/packages/webview_flutter/webview_flutter/CHANGELOG.md index 4eb0483a9f3c..a01b9f413929 100644 --- a/packages/webview_flutter/webview_flutter/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter/CHANGELOG.md @@ -1,3 +1,7 @@ +## 4.0.2 + +* Updates code for stricter lint checks. + ## 4.0.1 * Exposes `WebResourceErrorType` from platform interface. diff --git a/packages/webview_flutter/webview_flutter/lib/src/legacy/webview.dart b/packages/webview_flutter/webview_flutter/lib/src/legacy/webview.dart index 8d7baa9fa5af..d210e1e7669a 100644 --- a/packages/webview_flutter/webview_flutter/lib/src/legacy/webview.dart +++ b/packages/webview_flutter/webview_flutter/lib/src/legacy/webview.dart @@ -127,6 +127,7 @@ class WebView extends StatefulWidget { case TargetPlatform.iOS: _platform = CupertinoWebView(); break; + // ignore: no_default_cases default: throw UnsupportedError( "Trying to use the default webview implementation for $defaultTargetPlatform but there isn't a default one"); diff --git a/packages/webview_flutter/webview_flutter/pubspec.yaml b/packages/webview_flutter/webview_flutter/pubspec.yaml index ace6207d6236..99133ddd1129 100644 --- a/packages/webview_flutter/webview_flutter/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter/pubspec.yaml @@ -2,7 +2,7 @@ name: webview_flutter description: A Flutter plugin that provides a WebView widget on Android and iOS. repository: https://github.com/flutter/plugins/tree/main/packages/webview_flutter/webview_flutter issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22 -version: 4.0.1 +version: 4.0.2 environment: sdk: ">=2.17.0 <3.0.0" diff --git a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md index 9821c2c74e2e..569244b0ed3a 100644 --- a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## 3.2.1 + +* Updates code for stricter lint checks. + ## 3.2.0 * Adds support for handling file selection. See `AndroidWebViewController.setOnShowFileSelector`. diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart index f6e54b86d8a0..6f4af3ee7476 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart @@ -206,11 +206,17 @@ class AndroidWebViewController extends PlatformWebViewController { case LoadRequestMethod.post: return _webView.postUrl( params.uri.toString(), params.body ?? Uint8List(0)); - default: - throw UnimplementedError( - 'This version of `AndroidWebViewController` currently has no implementation for HTTP method ${params.method.serialize()} in loadRequest.', - ); } + // The enum comes from a different package, which could get a new value at + // any time, so a fallback case is necessary. Since there is no reasonable + // default behavior, throw to alert the client that they need an updated + // version. This is deliberately outside the switch rather than a `default` + // so that the linter will flag the switch as needing an update. + // ignore: dead_code + throw UnimplementedError( + 'This version of `AndroidWebViewController` currently has no ' + 'implementation for HTTP method ${params.method.serialize()} in ' + 'loadRequest.'); } @override diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/legacy/webview_android_widget.dart b/packages/webview_flutter/webview_flutter_android/lib/src/legacy/webview_android_widget.dart index 7e4ae9e6f9d1..cd4ba820cf4c 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/legacy/webview_android_widget.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/legacy/webview_android_widget.dart @@ -320,11 +320,17 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { case WebViewRequestMethod.post: return webView.postUrl( request.uri.toString(), request.body ?? Uint8List(0)); - default: - throw UnimplementedError( - 'This version of webview_android_widget currently has no implementation for HTTP method ${request.method.serialize()} in loadRequest.', - ); } + // The enum comes from a different package, which could get a new value at + // any time, so a fallback case is necessary. Since there is no reasonable + // default behavior, throw to alert the client that they need an updated + // version. This is deliberately outside the switch rather than a `default` + // so that the linter will flag the switch as needing an update. + // ignore: dead_code + throw UnimplementedError( + 'This version of webview_android_widget currently has no ' + 'implementation for HTTP method ${request.method.serialize()} in ' + 'loadRequest.'); } @override diff --git a/packages/webview_flutter/webview_flutter_android/pubspec.yaml b/packages/webview_flutter/webview_flutter_android/pubspec.yaml index b0088e22f403..eb761cca9104 100644 --- a/packages/webview_flutter/webview_flutter_android/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_android/pubspec.yaml @@ -2,7 +2,7 @@ name: webview_flutter_android description: A Flutter plugin that provides a WebView widget on Android. repository: https://github.com/flutter/plugins/tree/main/packages/webview_flutter/webview_flutter_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22 -version: 3.2.0 +version: 3.2.1 environment: sdk: ">=2.17.0 <3.0.0" From c44f56daa3f7d973b7519f620e635eafd27a402f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Jan 2023 18:23:05 +0000 Subject: [PATCH 014/130] [local_auth]: Bump mockito-inline (#6968) Bumps [mockito-inline](https://github.com/mockito/mockito) from 4.7.0 to 5.0.0. - [Release notes](https://github.com/mockito/mockito/releases) - [Commits](https://github.com/mockito/mockito/compare/v4.7.0...v5.0.0) --- updated-dependencies: - dependency-name: org.mockito:mockito-inline dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- packages/local_auth/local_auth_android/android/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/local_auth/local_auth_android/android/build.gradle b/packages/local_auth/local_auth_android/android/build.gradle index 58684a67e418..5eecba6278ee 100644 --- a/packages/local_auth/local_auth_android/android/build.gradle +++ b/packages/local_auth/local_auth_android/android/build.gradle @@ -55,7 +55,7 @@ dependencies { api "androidx.biometric:biometric:1.1.0" api "androidx.fragment:fragment:1.5.5" testImplementation 'junit:junit:4.13.2' - testImplementation 'org.mockito:mockito-inline:4.7.0' + testImplementation 'org.mockito:mockito-inline:5.0.0' testImplementation 'org.robolectric:robolectric:4.5' androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test:rules:1.2.0' From 4aa47dbdc581ca9149c7c059fd59c572ec3fd719 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Jan 2023 19:06:48 +0000 Subject: [PATCH 015/130] [camera]: Bump mockito-inline (#6978) Bumps [mockito-inline](https://github.com/mockito/mockito) from 4.7.0 to 5.0.0. - [Release notes](https://github.com/mockito/mockito/releases) - [Commits](https://github.com/mockito/mockito/compare/v4.7.0...v5.0.0) --- updated-dependencies: - dependency-name: org.mockito:mockito-inline dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- packages/camera/camera_android_camerax/android/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/camera/camera_android_camerax/android/build.gradle b/packages/camera/camera_android_camerax/android/build.gradle index 5eb60bb8ce64..1e04ad101670 100644 --- a/packages/camera/camera_android_camerax/android/build.gradle +++ b/packages/camera/camera_android_camerax/android/build.gradle @@ -62,7 +62,7 @@ dependencies { implementation "androidx.camera:camera-lifecycle:${camerax_version}" implementation 'com.google.guava:guava:31.1-android' testImplementation 'junit:junit:4.13.2' - testImplementation 'org.mockito:mockito-inline:4.7.0' + testImplementation 'org.mockito:mockito-inline:5.0.0' testImplementation 'androidx.test:core:1.4.0' testImplementation 'org.robolectric:robolectric:4.8' } From c2da106aefa41e313116128189dde15af39ffbb5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Jan 2023 19:06:50 +0000 Subject: [PATCH 016/130] [in_app_pur]: Bump mockito-core (#6961) Bumps [mockito-core](https://github.com/mockito/mockito) from 4.7.0 to 5.0.0. - [Release notes](https://github.com/mockito/mockito/releases) - [Commits](https://github.com/mockito/mockito/compare/v4.7.0...v5.0.0) --- updated-dependencies: - dependency-name: org.mockito:mockito-core dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../in_app_purchase/example/android/app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/in_app_purchase/in_app_purchase/example/android/app/build.gradle b/packages/in_app_purchase/in_app_purchase/example/android/app/build.gradle index 9bb39660cf05..b4a405c9d9b8 100644 --- a/packages/in_app_purchase/in_app_purchase/example/android/app/build.gradle +++ b/packages/in_app_purchase/in_app_purchase/example/android/app/build.gradle @@ -108,7 +108,7 @@ flutter { dependencies { implementation 'com.android.billingclient:billing:3.0.2' testImplementation 'junit:junit:4.13.2' - testImplementation 'org.mockito:mockito-core:4.7.0' + testImplementation 'org.mockito:mockito-core:5.0.0' testImplementation 'org.json:json:20220924' androidTestImplementation 'androidx.test:runner:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' From 41259226e8b92a353763ffe1c718a75b7166df0b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Jan 2023 19:07:52 +0000 Subject: [PATCH 017/130] [video_player]: Bump mockito-inline (#6964) Bumps [mockito-inline](https://github.com/mockito/mockito) from 4.7.0 to 5.0.0. - [Release notes](https://github.com/mockito/mockito/releases) - [Commits](https://github.com/mockito/mockito/compare/v4.7.0...v5.0.0) --- updated-dependencies: - dependency-name: org.mockito:mockito-inline dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- packages/video_player/video_player_android/android/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/video_player/video_player_android/android/build.gradle b/packages/video_player/video_player_android/android/build.gradle index 2677050d303b..daf3b8f4b83e 100644 --- a/packages/video_player/video_player_android/android/build.gradle +++ b/packages/video_player/video_player_android/android/build.gradle @@ -50,7 +50,7 @@ android { implementation 'com.google.android.exoplayer:exoplayer-smoothstreaming:2.18.1' testImplementation 'junit:junit:4.13.2' testImplementation 'androidx.test:core:1.3.0' - testImplementation 'org.mockito:mockito-inline:4.7.0' + testImplementation 'org.mockito:mockito-inline:5.0.0' testImplementation 'org.robolectric:robolectric:4.8.1' } From c4188b9708e2b18a7a454b131858d2aa5191d116 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Jan 2023 19:09:00 +0000 Subject: [PATCH 018/130] [quick_actions]: Bump mockito-android (#6970) Bumps [mockito-android](https://github.com/mockito/mockito) from 4.7.0 to 5.0.0. - [Release notes](https://github.com/mockito/mockito/releases) - [Commits](https://github.com/mockito/mockito/compare/v4.7.0...v5.0.0) --- updated-dependencies: - dependency-name: org.mockito:mockito-android dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../quick_actions_android/example/android/app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/quick_actions/quick_actions_android/example/android/app/build.gradle b/packages/quick_actions/quick_actions_android/example/android/app/build.gradle index 666194bc11b0..7d2bb3634cb2 100644 --- a/packages/quick_actions/quick_actions_android/example/android/app/build.gradle +++ b/packages/quick_actions/quick_actions_android/example/android/app/build.gradle @@ -63,5 +63,5 @@ dependencies { androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0' androidTestImplementation 'androidx.test.ext:junit:1.0.0' androidTestImplementation 'org.mockito:mockito-core:4.7.0' - androidTestImplementation 'org.mockito:mockito-android:4.7.0' + androidTestImplementation 'org.mockito:mockito-android:5.0.0' } From 3b1219dc5867bccc7d021b42fc01e9776a174a17 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Jan 2023 20:53:02 +0000 Subject: [PATCH 019/130] [camera]: Bump mockito-inline in /packages/camera/camera_android/android (#6973) Bumps [mockito-inline](https://github.com/mockito/mockito) from 4.7.0 to 5.0.0. - [Release notes](https://github.com/mockito/mockito/releases) - [Commits](https://github.com/mockito/mockito/compare/v4.7.0...v5.0.0) --- updated-dependencies: - dependency-name: org.mockito:mockito-inline dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- packages/camera/camera_android/android/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/camera/camera_android/android/build.gradle b/packages/camera/camera_android/android/build.gradle index 4fbb2270b556..7ed8ac77d371 100644 --- a/packages/camera/camera_android/android/build.gradle +++ b/packages/camera/camera_android/android/build.gradle @@ -63,7 +63,7 @@ android { dependencies { implementation 'androidx.annotation:annotation:1.5.0' testImplementation 'junit:junit:4.13.2' - testImplementation 'org.mockito:mockito-inline:4.7.0' + testImplementation 'org.mockito:mockito-inline:5.0.0' testImplementation 'androidx.test:core:1.4.0' testImplementation 'org.robolectric:robolectric:4.5' } From dac9e711560ae0d387e52c2dd752eda13297679f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Jan 2023 22:01:12 +0000 Subject: [PATCH 020/130] [sign_in]: Bump mockito-inline (#6962) Bumps [mockito-inline](https://github.com/mockito/mockito) from 4.7.0 to 5.0.0. - [Release notes](https://github.com/mockito/mockito/releases) - [Commits](https://github.com/mockito/mockito/compare/v4.7.0...v5.0.0) --- updated-dependencies: - dependency-name: org.mockito:mockito-inline dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../google_sign_in/google_sign_in_android/android/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google_sign_in/google_sign_in_android/android/build.gradle b/packages/google_sign_in/google_sign_in_android/android/build.gradle index 19d43aa3dee4..e9a609f5655d 100644 --- a/packages/google_sign_in/google_sign_in_android/android/build.gradle +++ b/packages/google_sign_in/google_sign_in_android/android/build.gradle @@ -53,5 +53,5 @@ dependencies { implementation 'com.google.android.gms:play-services-auth:20.4.0' implementation 'com.google.guava:guava:31.1-android' testImplementation 'junit:junit:4.13.2' - testImplementation 'org.mockito:mockito-inline:4.7.0' + testImplementation 'org.mockito:mockito-inline:5.0.0' } From 9091042cd99886ef176a19102e2c57418365d0df Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Jan 2023 22:02:50 +0000 Subject: [PATCH 021/130] [quick_actions]: Bump mockito-core (#6972) Bumps [mockito-core](https://github.com/mockito/mockito) from 4.7.0 to 5.0.0. - [Release notes](https://github.com/mockito/mockito/releases) - [Commits](https://github.com/mockito/mockito/compare/v4.7.0...v5.0.0) --- updated-dependencies: - dependency-name: org.mockito:mockito-core dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../quick_actions_android/example/android/app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/quick_actions/quick_actions_android/example/android/app/build.gradle b/packages/quick_actions/quick_actions_android/example/android/app/build.gradle index 7d2bb3634cb2..c9cbddb9ffeb 100644 --- a/packages/quick_actions/quick_actions_android/example/android/app/build.gradle +++ b/packages/quick_actions/quick_actions_android/example/android/app/build.gradle @@ -62,6 +62,6 @@ dependencies { androidTestImplementation "androidx.test:runner:$androidXTestVersion" androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0' androidTestImplementation 'androidx.test.ext:junit:1.0.0' - androidTestImplementation 'org.mockito:mockito-core:4.7.0' + androidTestImplementation 'org.mockito:mockito-core:5.0.0' androidTestImplementation 'org.mockito:mockito-android:5.0.0' } From c2ddef6fade73c017ccb860a29fc0dd41af68cc8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Jan 2023 22:02:52 +0000 Subject: [PATCH 022/130] [quick_actions]: Bump mockito-core (#6966) Bumps [mockito-core](https://github.com/mockito/mockito) from 4.7.0 to 5.0.0. - [Release notes](https://github.com/mockito/mockito/releases) - [Commits](https://github.com/mockito/mockito/compare/v4.7.0...v5.0.0) --- updated-dependencies: - dependency-name: org.mockito:mockito-core dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../quick_actions/quick_actions_android/android/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/quick_actions/quick_actions_android/android/build.gradle b/packages/quick_actions/quick_actions_android/android/build.gradle index e4cdec819ec9..e096e907ab70 100644 --- a/packages/quick_actions/quick_actions_android/android/build.gradle +++ b/packages/quick_actions/quick_actions_android/android/build.gradle @@ -37,7 +37,7 @@ android { dependencies { testImplementation 'junit:junit:4.13.2' - testImplementation 'org.mockito:mockito-core:4.7.0' + testImplementation 'org.mockito:mockito-core:5.0.0' } compileOptions { From d9cd91f22786765873387be09c5368c44bef9184 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Jan 2023 22:03:49 +0000 Subject: [PATCH 023/130] [video_player]: Bump mockito-core (#6965) Bumps [mockito-core](https://github.com/mockito/mockito) from 4.7.0 to 5.0.0. - [Release notes](https://github.com/mockito/mockito/releases) - [Commits](https://github.com/mockito/mockito/compare/v4.7.0...v5.0.0) --- updated-dependencies: - dependency-name: org.mockito:mockito-core dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../video_player_android/example/android/app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/video_player/video_player_android/example/android/app/build.gradle b/packages/video_player/video_player_android/example/android/app/build.gradle index 93caaa5c7c61..80de4a1ee27d 100644 --- a/packages/video_player/video_player_android/example/android/app/build.gradle +++ b/packages/video_player/video_player_android/example/android/app/build.gradle @@ -60,7 +60,7 @@ flutter { dependencies { testImplementation 'junit:junit:4.13' testImplementation 'org.robolectric:robolectric:4.8.2' - testImplementation 'org.mockito:mockito-core:4.7.0' + testImplementation 'org.mockito:mockito-core:5.0.0' androidTestImplementation 'androidx.test:runner:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' } From ab19b5fee6d4de140c059ea5bd16ac2da877323f Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Fri, 20 Jan 2023 17:58:04 -0500 Subject: [PATCH 024/130] Roll Flutter from 973cff40b402 to a07e8a6ac43d (60 revisions) (#7003) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 334898754 Add new macos target configured for flavors (flutter/flutter#117352) * 627752064 Roll Plugins from 4e5cf2d2da27 to 11361d01099d (4 revisions) (flutter/flutter#118682) * 997d43618 Fix applyBoxFit's handling of fitWidth and fitHeight. (flutter/flutter#117185) * 8a58ec5c3 Roll Flutter Engine from f79030440948 to c52b290813bd (29 revisions) (flutter/flutter#118720) * 374f09e1a [flutter_tools] No more implicit --no-sound-null-safety (flutter/flutter#118491) * ae1cc18c4 remove single-view assumption from `paintImage` (flutter/flutter#118721) * bb8b96a5d Fix path for require.js (flutter/flutter#118722) * c83a69855 update uikit view documentation (flutter/flutter#118715) * 2b3ca0dc4 Bump github/codeql-action from 2.1.38 to 2.1.39 (flutter/flutter#118735) * 666dccc85 [macOS] bringup new e2e_summary devicelab test (flutter/flutter#118717) * d07b88e4c Docs fix an=>a (flutter/flutter#118652) * 11d21e066 Add @pragma('vm:entry-point') to RestorableRouteBuilder arguments (flutter/flutter#118738) * 7d9eaab01 Appbar iconTheme override fix (flutter/flutter#118681) * 6f708305d Roll Flutter Engine from c52b290813bd to 290636c1cb6b (2 revisions) (flutter/flutter#118743) * b3059d2c0 Bump activesupport from 6.1.5 to 6.1.7.1 in /dev/ci/mac (flutter/flutter#118745) * ffcf63ae8 Add verbose flag to plugin_dependencies_test to debug flake (flutter/flutter#118755) * 2609212ca 2a11023c7 [ios_platform_view] more precision when determine if a clip rrect is necessary (flutter/engine#38965) (flutter/flutter#118751) * 21fb443a3 8ed6790b5 Bump chrome_and_driver version to 110. (flutter/engine#38986) (flutter/flutter#118758) * e5c9d065f Forgot to remove emulator flag. (flutter/flutter#118762) * 6a9b2db4a 95b0c151f Roll Dart SDK from 645fd748e79e to ddf70a598f27 (14 revisions) (flutter/engine#38990) (flutter/flutter#118763) * 0bbb5ec0c 40f7f0f09 Roll Fuchsia Mac SDK from P5QcCJU8I71xVXuMT... to tlYMsnCv86Fjt5LfF... (flutter/engine#38994) (flutter/flutter#118771) * d53cc4a10 [macOS] New e2e_summary benchmark fails without Cocoapods. (flutter/flutter#118754) * 3e71e0caf Updated `ListTile` documentation, add Material 3 example and other `ListTile` examples fixes. (flutter/flutter#118705) * 213b3cb3d Check whether slider is mounted before interaction, no-op if unmounted (flutter/flutter#113556) * 06909ccfa Update packages + fix tests for javascript mime change (flutter/flutter#118617) * 46c7fd14d 88e61d8bd Remove references to Observatory (flutter/engine#38919) (flutter/flutter#118793) * b9ab64049 Remove incorrect statement in documentation (flutter/flutter#118636) * ea36b3a5a Add focus detector to CupertinoSwitch (flutter/flutter#118345) * 9b5ea30a9 Switching over from iOS-15 to iOS-16 in .ci.yaml. (flutter/flutter#118807) * 67ffaef25 29a0582a1 Roll Fuchsia Mac SDK from tlYMsnCv86Fjt5LfF... to 6oiZwMyNsjucSxTHJ... (flutter/engine#39004) (flutter/flutter#118817) * 5cd2d4c61 Support iOS wireless debugging (flutter/flutter#118104) * cbf2e1689 Revert "Support iOS wireless debugging (#118104)" (flutter/flutter#118826) * 2258590a8 Do not run Mac_arm64_ios run_debug_test_macos in presubmit during iPhone 11 migration (flutter/flutter#118828) * 1dd7f45bf Add `build macos --config-only` option. (flutter/flutter#118649) * 22520f54d [macOS] Add timeline summary benchmarks (flutter/flutter#118748) * 99e4ca50c Roll Flutter Engine from 29a0582a1d5f to 78bbea005d27 (2 revisions) (flutter/flutter#118829) * c5ceff11d [flutter_tools] Ensure service worker starts caching assets since first load (flutter/flutter#116833) * 818bb4e65 Roll Flutter Engine from 78bbea005d27 to 26b6609c603b (3 revisions) (flutter/flutter#118839) * 09bd0f661 Support logging 'flutter run' communication to DAP clients (flutter/flutter#118674) * 73096fd96 [macos] add flavor options to commands in the `flutter_tool` (flutter/flutter#118421) * 030288d33 Revert "[macos] add flavor options to commands in the `flutter_tool` (#118421)" (flutter/flutter#118858) * 9acf34d0d Roll Flutter Engine from 26b6609c603b to 7d40e77d0035 (2 revisions) (flutter/flutter#118852) * ec51d3271 Remove unnecessary null checks in ‘dev/conductor’ (flutter/flutter#118843) * 54217bd4b Remove unnecessary null checks in `dev/benchmarks` (flutter/flutter#118840) * 98c18ca93 Remove unnecessary null checks in examples/ (flutter/flutter#118848) * 99b5262b2 Remove unnecessary null checks in dev/tools (flutter/flutter#118845) * 52d1205b8 Roll Flutter Engine from 7d40e77d0035 to 730e88fb6787 (3 revisions) (flutter/flutter#118869) * ee9c9b692 3876320cb Roll Skia from aedfc8695954 to 1b3aa8b6e1cc (43 revisions) (flutter/engine#39024) (flutter/flutter#118871) * 589f2eb9e d2436a536 Extract WideToUTF16String/UTF16StringToWide to FML (flutter/engine#39020) (flutter/flutter#118873) * 74645b43a Fix `NavigationBar` indicator ripple doesn't account for label height (flutter/flutter#117473) * f78b1f351 dfe67f4c7 Roll Skia from 1b3aa8b6e1cc to f6a5c806294d (11 revisions) (flutter/engine#39027) (flutter/flutter#118874) * 572f0a1a9 66e177a3d Roll Dart SDK from ddf70a598f27 to fbbfc122dba6 (9 revisions) (flutter/engine#39029) (flutter/flutter#118878) * 26472b59c ccccee513 [macos] Synthesize modifier keys events on pointer events (flutter/engine#37870) (flutter/flutter#118880) * 095b1abda Checkbox borderSide lerp bug fix (flutter/flutter#118728) * ec6ff90ab Roll Flutter Engine from ccccee513fb2 to d84b3dc74c9f (2 revisions) (flutter/flutter#118893) * 492d57262 Cleanup obsolete --compact-async compiler option (flutter/flutter#118894) * f291eb349 Remove unnecessary null checks in integration_test (flutter/flutter#118861) * ab3c82244 Remove unnecessary null checks in dev/devicelab (flutter/flutter#118842) * bf72f5ebf 58eb1061e Revert "Remove references to Observatory (#38919)" (flutter/engine#39035) (flutter/flutter#118899) * a07e8a6ac [reland] Support wireless debugging (flutter/flutter#118895) --- .ci/flutter_master.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/flutter_master.version b/.ci/flutter_master.version index c177696ee30a..b754ae40b55c 100644 --- a/.ci/flutter_master.version +++ b/.ci/flutter_master.version @@ -1 +1 @@ -973cff40b4022edd59dbf44475ffc569bece1ea8 +a07e8a6ac43d4582c86174592ebe45edc37d115a From d610a2135ec500474106c386590770492ea5f7c0 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Sun, 22 Jan 2023 13:35:23 -0500 Subject: [PATCH 025/130] Roll Flutter from a07e8a6ac43d to f33e8d3701b5 (24 revisions) (#7007) * 3c769effa Cupertino navbar ellipsis fix (flutter/flutter#118841) * d1be731c6 3fead63ba Roll Dart SDK from ac4c63168ff2 to 03d35455a8d8 (1 revision) (flutter/engine#39036) (flutter/flutter#118909) * c0ad6add2 Marks Mac plugin_test_macos to be unflaky (flutter/flutter#118706) * 83720015a Remove unnecessary null checks in flutter_test (flutter/flutter#118865) * 288a7733e Remove unnecessary null checks in flutter_driver (flutter/flutter#118864) * bb73121cb Remove unnecessary null checks in flutter/test (flutter/flutter#118905) * 15bc4e466 Marks Mac_android microbenchmarks to be flaky (flutter/flutter#118693) * 1cdaf9e33 e2c2e5009 [impeller] correct input order in ColorFilterContents::MakeBlend (flutter/engine#39038) (flutter/flutter#118913) * 49e025d8a Update android defines test to use emulator (flutter/flutter#118808) * bae4c1d24 Revert "Update android defines test to use emulator (#118808)" (flutter/flutter#118928) * 9837eb6fc Remove unnecessary null checks in flutter/rendering (flutter/flutter#118923) * 25843bdb5 Remove macOS impeller benchmarks (flutter/flutter#118917) * 70cecf6c9 Remove unnecessary null checks in dev/*_tests (flutter/flutter#118844) * c757df3bf Remove unnecessary null checks in dev/bots (flutter/flutter#118846) * 5d74b5cbf Remove unnecessary null checks in flutter/painting (flutter/flutter#118925) * 7272c809e Remove unnecessary null checks in `flutter/{animation,semantics,scheduler}` (flutter/flutter#118922) * 2baea2f62 7b68d71b8 Roll Dart SDK from 03d35455a8d8 to 807077cc5d1b (1 revision) (flutter/engine#39042) (flutter/flutter#118933) * 8d60a8c0b Roll Flutter Engine from 7b68d71b8d03 to 3a444b36657c (3 revisions) (flutter/flutter#118938) * 5ccdb8107 bb4e8dfe2 Roll Fuchsia Linux SDK from rPo4_TYHCtkoOfRup... to S6wQW1tLFe-YnReaZ... (flutter/engine#39048) (flutter/flutter#118942) * b1f4070d5 ef7b1856a Roll Dart SDK from 8c2eb20b5376 to 548678dd684c (1 revision) (flutter/engine#39049) (flutter/flutter#118944) * 80658873b Add transform flip (flutter/flutter#116705) * 68b6e720c 406dce64f Roll Fuchsia Mac SDK from ZTKDeVL1HDAwsZdhl... to l7jVM3Urw73TVWfee... (flutter/engine#39050) (flutter/flutter#118964) * cf628add4 aa194347a Roll Fuchsia Linux SDK from S6wQW1tLFe-YnReaZ... to l3c_b-vRr-o6ZFX_M... (flutter/engine#39055) (flutter/flutter#118968) * f33e8d370 2a2dfaafb Roll Fuchsia Mac SDK from l7jVM3Urw73TVWfee... to 5TQ9IL4-Yu3KHCR-H... (flutter/engine#39056) (flutter/flutter#118969) --- .ci/flutter_master.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/flutter_master.version b/.ci/flutter_master.version index b754ae40b55c..c4700a45fa39 100644 --- a/.ci/flutter_master.version +++ b/.ci/flutter_master.version @@ -1 +1 @@ -a07e8a6ac43d4582c86174592ebe45edc37d115a +f33e8d3701b5c208aece326f14bf3a323c65cca1 From db4d20c5deb88c325f554e4fe8f8d57b87aff15c Mon Sep 17 00:00:00 2001 From: Alejandro Pinola <57049542+adpinola@users.noreply.github.com> Date: Sun, 22 Jan 2023 20:36:14 -0300 Subject: [PATCH 026/130] [file_selector] add getDirectoryPaths implementation on Linux (#6573) * Add getDirectoriesPaths method to the file_selector_platform_interface Add getDirectoriesPaths to method channel. Increment version to 2.3.0 apply feedback extract assertion method * add getDirectoryPaths Linux implementation * apply rebase * update version to 0.9.1 Co-authored-by: eugerossetto --- .../file_selector_linux/CHANGELOG.md | 4 + .../lib/get_multiple_directories_page.dart | 84 ++++ .../example/lib/home_page.dart | 7 + .../file_selector_linux/example/lib/main.dart | 3 + .../file_selector_linux/example/pubspec.yaml | 4 +- .../lib/file_selector_linux.dart | 27 +- .../file_selector_linux/linux/.gitignore | 2 + .../linux/file_selector_plugin.cc | 6 +- .../linux/test/file_selector_plugin_test.cc | 14 + .../file_selector_linux/pubspec.yaml | 4 +- .../test/file_selector_linux_test.dart | 367 ++++++++++-------- 11 files changed, 343 insertions(+), 179 deletions(-) create mode 100644 packages/file_selector/file_selector_linux/example/lib/get_multiple_directories_page.dart create mode 100644 packages/file_selector/file_selector_linux/linux/.gitignore diff --git a/packages/file_selector/file_selector_linux/CHANGELOG.md b/packages/file_selector/file_selector_linux/CHANGELOG.md index a1f57b5cc857..70ce307180d8 100644 --- a/packages/file_selector/file_selector_linux/CHANGELOG.md +++ b/packages/file_selector/file_selector_linux/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.9.1 + +* Adds `getDirectoryPaths` implementation. + ## 0.9.0+1 * Changes XTypeGroup initialization from final to const. diff --git a/packages/file_selector/file_selector_linux/example/lib/get_multiple_directories_page.dart b/packages/file_selector/file_selector_linux/example/lib/get_multiple_directories_page.dart new file mode 100644 index 000000000000..66ab29cfdd9b --- /dev/null +++ b/packages/file_selector/file_selector_linux/example/lib/get_multiple_directories_page.dart @@ -0,0 +1,84 @@ +// Copyright 2013 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. + +import 'package:file_selector_platform_interface/file_selector_platform_interface.dart'; +import 'package:flutter/material.dart'; + +/// Screen that allows the user to select one or more directories using `getDirectoryPaths`, +/// then displays the selected directories in a dialog. +class GetMultipleDirectoriesPage extends StatelessWidget { + /// Default Constructor + const GetMultipleDirectoriesPage({Key? key}) : super(key: key); + + Future _getDirectoryPaths(BuildContext context) async { + const String confirmButtonText = 'Choose'; + final List directoryPaths = + await FileSelectorPlatform.instance.getDirectoryPaths( + confirmButtonText: confirmButtonText, + ); + if (directoryPaths.isEmpty) { + // Operation was canceled by the user. + return; + } + await showDialog( + context: context, + builder: (BuildContext context) => TextDisplay(directoryPaths.join('\n')), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Select multiple directories'), + ), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ElevatedButton( + style: ElevatedButton.styleFrom( + // TODO(darrenaustin): Migrate to new API once it lands in stable: https://github.com/flutter/flutter/issues/105724 + // ignore: deprecated_member_use + primary: Colors.blue, + // ignore: deprecated_member_use + onPrimary: Colors.white, + ), + child: const Text( + 'Press to ask user to choose multiple directories'), + onPressed: () => _getDirectoryPaths(context), + ), + ], + ), + ), + ); + } +} + +/// Widget that displays a text file in a dialog. +class TextDisplay extends StatelessWidget { + /// Creates a `TextDisplay`. + const TextDisplay(this.directoriesPaths, {Key? key}) : super(key: key); + + /// The path selected in the dialog. + final String directoriesPaths; + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: const Text('Selected Directories'), + content: Scrollbar( + child: SingleChildScrollView( + child: Text(directoriesPaths), + ), + ), + actions: [ + TextButton( + child: const Text('Close'), + onPressed: () => Navigator.pop(context), + ), + ], + ); + } +} diff --git a/packages/file_selector/file_selector_linux/example/lib/home_page.dart b/packages/file_selector/file_selector_linux/example/lib/home_page.dart index a4b2ae1f63ea..80e16332a017 100644 --- a/packages/file_selector/file_selector_linux/example/lib/home_page.dart +++ b/packages/file_selector/file_selector_linux/example/lib/home_page.dart @@ -55,6 +55,13 @@ class HomePage extends StatelessWidget { child: const Text('Open a get directory dialog'), onPressed: () => Navigator.pushNamed(context, '/directory'), ), + const SizedBox(height: 10), + ElevatedButton( + style: style, + child: const Text('Open a get directories dialog'), + onPressed: () => + Navigator.pushNamed(context, '/multi-directories'), + ), ], ), ), diff --git a/packages/file_selector/file_selector_linux/example/lib/main.dart b/packages/file_selector/file_selector_linux/example/lib/main.dart index 3e447104ef9f..b8f047645a1d 100644 --- a/packages/file_selector/file_selector_linux/example/lib/main.dart +++ b/packages/file_selector/file_selector_linux/example/lib/main.dart @@ -5,6 +5,7 @@ import 'package:flutter/material.dart'; import 'get_directory_page.dart'; +import 'get_multiple_directories_page.dart'; import 'home_page.dart'; import 'open_image_page.dart'; import 'open_multiple_images_page.dart'; @@ -36,6 +37,8 @@ class MyApp extends StatelessWidget { '/open/text': (BuildContext context) => const OpenTextPage(), '/save/text': (BuildContext context) => SaveTextPage(), '/directory': (BuildContext context) => const GetDirectoryPage(), + '/multi-directories': (BuildContext context) => + const GetMultipleDirectoriesPage() }, ); } diff --git a/packages/file_selector/file_selector_linux/example/pubspec.yaml b/packages/file_selector/file_selector_linux/example/pubspec.yaml index 51bdb28717aa..912a082bd602 100644 --- a/packages/file_selector/file_selector_linux/example/pubspec.yaml +++ b/packages/file_selector/file_selector_linux/example/pubspec.yaml @@ -1,6 +1,6 @@ name: file_selector_linux_example description: Local testbed for Linux file_selector implementation. -publish_to: 'none' # Remove this line if you wish to publish to pub.dev +publish_to: 'none' version: 1.0.0+1 environment: @@ -9,7 +9,7 @@ environment: dependencies: file_selector_linux: path: ../ - file_selector_platform_interface: ^2.2.0 + file_selector_platform_interface: ^2.4.0 flutter: sdk: flutter diff --git a/packages/file_selector/file_selector_linux/lib/file_selector_linux.dart b/packages/file_selector/file_selector_linux/lib/file_selector_linux.dart index 430b41c398db..b8e3df6a11bd 100644 --- a/packages/file_selector/file_selector_linux/lib/file_selector_linux.dart +++ b/packages/file_selector/file_selector_linux/lib/file_selector_linux.dart @@ -102,13 +102,26 @@ class FileSelectorLinux extends FileSelectorPlatform { String? initialDirectory, String? confirmButtonText, }) async { - return _channel.invokeMethod( - _getDirectoryPathMethod, - { - _initialDirectoryKey: initialDirectory, - _confirmButtonTextKey: confirmButtonText, - }, - ); + final List? path = await _channel + .invokeListMethod(_getDirectoryPathMethod, { + _initialDirectoryKey: initialDirectory, + _confirmButtonTextKey: confirmButtonText, + }); + return path?.first; + } + + @override + Future> getDirectoryPaths({ + String? initialDirectory, + String? confirmButtonText, + }) async { + final List? pathList = await _channel + .invokeListMethod(_getDirectoryPathMethod, { + _initialDirectoryKey: initialDirectory, + _confirmButtonTextKey: confirmButtonText, + _multipleKey: true, + }); + return pathList ?? []; } } diff --git a/packages/file_selector/file_selector_linux/linux/.gitignore b/packages/file_selector/file_selector_linux/linux/.gitignore new file mode 100644 index 000000000000..83fee186aa98 --- /dev/null +++ b/packages/file_selector/file_selector_linux/linux/.gitignore @@ -0,0 +1,2 @@ +CMakeCache.txt +CMakeFiles/ \ No newline at end of file diff --git a/packages/file_selector/file_selector_linux/linux/file_selector_plugin.cc b/packages/file_selector/file_selector_linux/linux/file_selector_plugin.cc index 833771955120..5a8cc2132595 100644 --- a/packages/file_selector/file_selector_linux/linux/file_selector_plugin.cc +++ b/packages/file_selector/file_selector_linux/linux/file_selector_plugin.cc @@ -192,10 +192,10 @@ static void method_call_cb(FlMethodChannel* channel, FlMethodCall* method_call, FlValue* args = fl_method_call_get_args(method_call); g_autoptr(FlMethodResponse) response = nullptr; - if (strcmp(method, kOpenFileMethod) == 0) { + if (strcmp(method, kOpenFileMethod) == 0 || + strcmp(method, kGetDirectoryPathMethod) == 0) { response = show_dialog(self, method, args, true); - } else if (strcmp(method, kGetDirectoryPathMethod) == 0 || - strcmp(method, kGetSavePathMethod) == 0) { + } else if (strcmp(method, kGetSavePathMethod) == 0) { response = show_dialog(self, method, args, false); } else { response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new()); diff --git a/packages/file_selector/file_selector_linux/linux/test/file_selector_plugin_test.cc b/packages/file_selector/file_selector_linux/linux/test/file_selector_plugin_test.cc index 84c55ac91900..8762b4a5f9f6 100644 --- a/packages/file_selector/file_selector_linux/linux/test/file_selector_plugin_test.cc +++ b/packages/file_selector/file_selector_linux/linux/test/file_selector_plugin_test.cc @@ -169,3 +169,17 @@ TEST(FileSelectorPlugin, TestGetDirectory) { EXPECT_EQ(gtk_file_chooser_get_select_multiple(GTK_FILE_CHOOSER(dialog)), false); } + +TEST(FileSelectorPlugin, TestGetMultipleDirectories) { + g_autoptr(FlValue) args = fl_value_new_map(); + fl_value_set_string_take(args, "multiple", fl_value_new_bool(true)); + + g_autoptr(GtkFileChooserNative) dialog = + create_dialog_for_method(nullptr, "getDirectoryPath", args); + + ASSERT_NE(dialog, nullptr); + EXPECT_EQ(gtk_file_chooser_get_action(GTK_FILE_CHOOSER(dialog)), + GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER); + EXPECT_EQ(gtk_file_chooser_get_select_multiple(GTK_FILE_CHOOSER(dialog)), + true); +} diff --git a/packages/file_selector/file_selector_linux/pubspec.yaml b/packages/file_selector/file_selector_linux/pubspec.yaml index a8aea37d72e2..5aff9493d28f 100644 --- a/packages/file_selector/file_selector_linux/pubspec.yaml +++ b/packages/file_selector/file_selector_linux/pubspec.yaml @@ -2,7 +2,7 @@ name: file_selector_linux description: Liunx implementation of the file_selector plugin. repository: https://github.com/flutter/plugins/tree/main/packages/file_selector/file_selector_linux issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+file_selector%22 -version: 0.9.0+1 +version: 0.9.1 environment: sdk: ">=2.12.0 <3.0.0" @@ -18,7 +18,7 @@ flutter: dependencies: cross_file: ^0.3.1 - file_selector_platform_interface: ^2.2.0 + file_selector_platform_interface: ^2.4.0 flutter: sdk: flutter diff --git a/packages/file_selector/file_selector_linux/test/file_selector_linux_test.dart b/packages/file_selector/file_selector_linux/test/file_selector_linux_test.dart index 748f922ae6ef..4eae078def0c 100644 --- a/packages/file_selector/file_selector_linux/test/file_selector_linux_test.dart +++ b/packages/file_selector/file_selector_linux/test/file_selector_linux_test.dart @@ -46,57 +46,54 @@ void main() { await plugin.openFile(acceptedTypeGroups: [group, groupTwo]); - expect( + expectMethodCall( log, - [ - isMethodCall('openFile', arguments: { - 'acceptedTypeGroups': >[ - { - 'label': 'text', - 'extensions': ['*.txt'], - 'mimeTypes': ['text/plain'], - }, - { - 'label': 'image', - 'extensions': ['*.jpg'], - 'mimeTypes': ['image/jpg'], - }, - ], - 'initialDirectory': null, - 'confirmButtonText': null, - 'multiple': false, - }), - ], + 'openFile', + arguments: { + 'acceptedTypeGroups': >[ + { + 'label': 'text', + 'extensions': ['*.txt'], + 'mimeTypes': ['text/plain'], + }, + { + 'label': 'image', + 'extensions': ['*.jpg'], + 'mimeTypes': ['image/jpg'], + }, + ], + 'initialDirectory': null, + 'confirmButtonText': null, + 'multiple': false, + }, ); }); test('passes initialDirectory correctly', () async { await plugin.openFile(initialDirectory: '/example/directory'); - expect( + expectMethodCall( log, - [ - isMethodCall('openFile', arguments: { - 'initialDirectory': '/example/directory', - 'confirmButtonText': null, - 'multiple': false, - }), - ], + 'openFile', + arguments: { + 'initialDirectory': '/example/directory', + 'confirmButtonText': null, + 'multiple': false, + }, ); }); test('passes confirmButtonText correctly', () async { await plugin.openFile(confirmButtonText: 'Open File'); - expect( + expectMethodCall( log, - [ - isMethodCall('openFile', arguments: { - 'initialDirectory': null, - 'confirmButtonText': 'Open File', - 'multiple': false, - }), - ], + 'openFile', + arguments: { + 'initialDirectory': null, + 'confirmButtonText': 'Open File', + 'multiple': false, + }, ); }); @@ -118,21 +115,20 @@ void main() { await plugin.openFile(acceptedTypeGroups: [group]); - expect( + expectMethodCall( log, - [ - isMethodCall('openFile', arguments: { - 'acceptedTypeGroups': >[ - { - 'label': 'any', - 'extensions': ['*'], - }, - ], - 'initialDirectory': null, - 'confirmButtonText': null, - 'multiple': false, - }), - ], + 'openFile', + arguments: { + 'acceptedTypeGroups': >[ + { + 'label': 'any', + 'extensions': ['*'], + }, + ], + 'initialDirectory': null, + 'confirmButtonText': null, + 'multiple': false, + }, ); }); }); @@ -156,57 +152,54 @@ void main() { await plugin.openFiles(acceptedTypeGroups: [group, groupTwo]); - expect( + expectMethodCall( log, - [ - isMethodCall('openFile', arguments: { - 'acceptedTypeGroups': >[ - { - 'label': 'text', - 'extensions': ['*.txt'], - 'mimeTypes': ['text/plain'], - }, - { - 'label': 'image', - 'extensions': ['*.jpg'], - 'mimeTypes': ['image/jpg'], - }, - ], - 'initialDirectory': null, - 'confirmButtonText': null, - 'multiple': true, - }), - ], + 'openFile', + arguments: { + 'acceptedTypeGroups': >[ + { + 'label': 'text', + 'extensions': ['*.txt'], + 'mimeTypes': ['text/plain'], + }, + { + 'label': 'image', + 'extensions': ['*.jpg'], + 'mimeTypes': ['image/jpg'], + }, + ], + 'initialDirectory': null, + 'confirmButtonText': null, + 'multiple': true, + }, ); }); test('passes initialDirectory correctly', () async { await plugin.openFiles(initialDirectory: '/example/directory'); - expect( + expectMethodCall( log, - [ - isMethodCall('openFile', arguments: { - 'initialDirectory': '/example/directory', - 'confirmButtonText': null, - 'multiple': true, - }), - ], + 'openFile', + arguments: { + 'initialDirectory': '/example/directory', + 'confirmButtonText': null, + 'multiple': true, + }, ); }); test('passes confirmButtonText correctly', () async { await plugin.openFiles(confirmButtonText: 'Open File'); - expect( + expectMethodCall( log, - [ - isMethodCall('openFile', arguments: { - 'initialDirectory': null, - 'confirmButtonText': 'Open File', - 'multiple': true, - }), - ], + 'openFile', + arguments: { + 'initialDirectory': null, + 'confirmButtonText': 'Open File', + 'multiple': true, + }, ); }); @@ -228,21 +221,20 @@ void main() { await plugin.openFile(acceptedTypeGroups: [group]); - expect( + expectMethodCall( log, - [ - isMethodCall('openFile', arguments: { - 'acceptedTypeGroups': >[ - { - 'label': 'any', - 'extensions': ['*'], - }, - ], - 'initialDirectory': null, - 'confirmButtonText': null, - 'multiple': false, - }), - ], + 'openFile', + arguments: { + 'acceptedTypeGroups': >[ + { + 'label': 'any', + 'extensions': ['*'], + }, + ], + 'initialDirectory': null, + 'confirmButtonText': null, + 'multiple': false, + }, ); }); }); @@ -267,57 +259,54 @@ void main() { await plugin .getSavePath(acceptedTypeGroups: [group, groupTwo]); - expect( + expectMethodCall( log, - [ - isMethodCall('getSavePath', arguments: { - 'acceptedTypeGroups': >[ - { - 'label': 'text', - 'extensions': ['*.txt'], - 'mimeTypes': ['text/plain'], - }, - { - 'label': 'image', - 'extensions': ['*.jpg'], - 'mimeTypes': ['image/jpg'], - }, - ], - 'initialDirectory': null, - 'suggestedName': null, - 'confirmButtonText': null, - }), - ], + 'getSavePath', + arguments: { + 'acceptedTypeGroups': >[ + { + 'label': 'text', + 'extensions': ['*.txt'], + 'mimeTypes': ['text/plain'], + }, + { + 'label': 'image', + 'extensions': ['*.jpg'], + 'mimeTypes': ['image/jpg'], + }, + ], + 'initialDirectory': null, + 'suggestedName': null, + 'confirmButtonText': null, + }, ); }); test('passes initialDirectory correctly', () async { await plugin.getSavePath(initialDirectory: '/example/directory'); - expect( + expectMethodCall( log, - [ - isMethodCall('getSavePath', arguments: { - 'initialDirectory': '/example/directory', - 'suggestedName': null, - 'confirmButtonText': null, - }), - ], + 'getSavePath', + arguments: { + 'initialDirectory': '/example/directory', + 'suggestedName': null, + 'confirmButtonText': null, + }, ); }); test('passes confirmButtonText correctly', () async { await plugin.getSavePath(confirmButtonText: 'Open File'); - expect( + expectMethodCall( log, - [ - isMethodCall('getSavePath', arguments: { - 'initialDirectory': null, - 'suggestedName': null, - 'confirmButtonText': 'Open File', - }), - ], + 'getSavePath', + arguments: { + 'initialDirectory': null, + 'suggestedName': null, + 'confirmButtonText': 'Open File', + }, ); }); @@ -339,21 +328,20 @@ void main() { await plugin.openFile(acceptedTypeGroups: [group]); - expect( + expectMethodCall( log, - [ - isMethodCall('openFile', arguments: { - 'acceptedTypeGroups': >[ - { - 'label': 'any', - 'extensions': ['*'], - }, - ], - 'initialDirectory': null, - 'confirmButtonText': null, - 'multiple': false, - }), - ], + 'openFile', + arguments: { + 'acceptedTypeGroups': >[ + { + 'label': 'any', + 'extensions': ['*'], + }, + ], + 'initialDirectory': null, + 'confirmButtonText': null, + 'multiple': false, + }, ); }); }); @@ -362,28 +350,77 @@ void main() { test('passes initialDirectory correctly', () async { await plugin.getDirectoryPath(initialDirectory: '/example/directory'); - expect( + expectMethodCall( log, - [ - isMethodCall('getDirectoryPath', arguments: { - 'initialDirectory': '/example/directory', - 'confirmButtonText': null, - }), - ], + 'getDirectoryPath', + arguments: { + 'initialDirectory': '/example/directory', + 'confirmButtonText': null, + }, ); }); test('passes confirmButtonText correctly', () async { - await plugin.getDirectoryPath(confirmButtonText: 'Open File'); + await plugin.getDirectoryPath(confirmButtonText: 'Select Folder'); - expect( + expectMethodCall( log, - [ - isMethodCall('getDirectoryPath', arguments: { - 'initialDirectory': null, - 'confirmButtonText': 'Open File', - }), - ], + 'getDirectoryPath', + arguments: { + 'initialDirectory': null, + 'confirmButtonText': 'Select Folder', + }, ); }); }); + + group('#getDirectoryPaths', () { + test('passes initialDirectory correctly', () async { + await plugin.getDirectoryPaths(initialDirectory: '/example/directory'); + + expectMethodCall( + log, + 'getDirectoryPath', + arguments: { + 'initialDirectory': '/example/directory', + 'confirmButtonText': null, + 'multiple': true, + }, + ); + }); + test('passes confirmButtonText correctly', () async { + await plugin.getDirectoryPaths( + confirmButtonText: 'Select one or mode folders'); + + expectMethodCall( + log, + 'getDirectoryPath', + arguments: { + 'initialDirectory': null, + 'confirmButtonText': 'Select one or mode folders', + 'multiple': true, + }, + ); + }); + test('passes multiple flag correctly', () async { + await plugin.getDirectoryPaths(); + + expectMethodCall( + log, + 'getDirectoryPath', + arguments: { + 'initialDirectory': null, + 'confirmButtonText': null, + 'multiple': true, + }, + ); + }); + }); +} + +void expectMethodCall( + List log, + String methodName, { + Map? arguments, +}) { + expect(log, [isMethodCall(methodName, arguments: arguments)]); } From e9cb4d2c5798a536f73094827abc0c2f9baf71fa Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Mon, 23 Jan 2023 12:04:24 -0500 Subject: [PATCH 027/130] Roll Flutter from f33e8d3701b5 to bd7bee0f9eb8 (5 revisions) (#7010) * 044e344a7 a8522271c Roll Fuchsia Mac SDK from 5TQ9IL4-Yu3KHCR-H... to R4F4q-h902yt4s7ow... (flutter/engine#39058) (flutter/flutter#118984) * b974eac8e b3da52d8c Roll Fuchsia Linux SDK from l3c_b-vRr-o6ZFX_M... to f613tOkDB282hW2tA... (flutter/engine#39061) (flutter/flutter#118987) * 696a84b1e 1e4e11ad1 Add more flexible image loading API (flutter/engine#38905) (flutter/flutter#118989) * 6ae7ad72c 92313596d Roll Dart SDK from 548678dd684c to 608a0691a1d7 (1 revision) (flutter/engine#39063) (flutter/flutter#118990) * bd7bee0f9 Roll Flutter Engine from 92313596d77b to 8e7bc509e0d7 (3 revisions) (flutter/flutter#119004) --- .ci/flutter_master.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/flutter_master.version b/.ci/flutter_master.version index c4700a45fa39..1f9e5aac2b11 100644 --- a/.ci/flutter_master.version +++ b/.ci/flutter_master.version @@ -1 +1 @@ -f33e8d3701b5c208aece326f14bf3a323c65cca1 +bd7bee0f9eb80625a5db1d8ffc35710dd283215f From 729c14a9243ebd2b16d7ec2a63b4e54661b00641 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Jan 2023 19:12:29 +0000 Subject: [PATCH 028/130] [sign_in]: Bump play-services-auth from 20.4.0 to 20.4.1 in /packages/google_sign_in/google_sign_in_android/android (#7008) * [sign_in]: Bump play-services-auth Bumps play-services-auth from 20.4.0 to 20.4.1. --- updated-dependencies: - dependency-name: com.google.android.gms:play-services-auth dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * Bump version Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: camsim99 --- packages/google_sign_in/google_sign_in_android/CHANGELOG.md | 4 ++++ .../google_sign_in_android/android/build.gradle | 2 +- packages/google_sign_in/google_sign_in_android/pubspec.yaml | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/google_sign_in/google_sign_in_android/CHANGELOG.md b/packages/google_sign_in/google_sign_in_android/CHANGELOG.md index 9775c409de43..e03ca2dc7297 100644 --- a/packages/google_sign_in/google_sign_in_android/CHANGELOG.md +++ b/packages/google_sign_in/google_sign_in_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## 6.1.5 + +* Updates play-services-auth version to 20.4.1. + ## 6.1.4 * Rolls Guava to version 31.1. diff --git a/packages/google_sign_in/google_sign_in_android/android/build.gradle b/packages/google_sign_in/google_sign_in_android/android/build.gradle index e9a609f5655d..9bc00197e03b 100644 --- a/packages/google_sign_in/google_sign_in_android/android/build.gradle +++ b/packages/google_sign_in/google_sign_in_android/android/build.gradle @@ -50,7 +50,7 @@ android { } dependencies { - implementation 'com.google.android.gms:play-services-auth:20.4.0' + implementation 'com.google.android.gms:play-services-auth:20.4.1' implementation 'com.google.guava:guava:31.1-android' testImplementation 'junit:junit:4.13.2' testImplementation 'org.mockito:mockito-inline:5.0.0' diff --git a/packages/google_sign_in/google_sign_in_android/pubspec.yaml b/packages/google_sign_in/google_sign_in_android/pubspec.yaml index 350fe450f940..ccf212dd3e71 100644 --- a/packages/google_sign_in/google_sign_in_android/pubspec.yaml +++ b/packages/google_sign_in/google_sign_in_android/pubspec.yaml @@ -2,7 +2,7 @@ name: google_sign_in_android description: Android implementation of the google_sign_in plugin. repository: https://github.com/flutter/plugins/tree/main/packages/google_sign_in/google_sign_in_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+google_sign_in%22 -version: 6.1.4 +version: 6.1.5 environment: sdk: ">=2.14.0 <3.0.0" From d649e18505dde11facdbcd220a8ed5b832ae339b Mon Sep 17 00:00:00 2001 From: stuartmorgan Date: Tue, 24 Jan 2023 10:33:42 -0800 Subject: [PATCH 029/130] [local_auth] Convert Windows to Pigeon (#7012) Updates `local_auth_windows` to use Pigeon, and moves to a more platform-tailored and Dart-centric implementation (rather than keeping the previous cross-platform method channel interface that the current implementation was duplicated from): - Eliminates `deviceSupportsBiometrics` from the platform interface, since it's always the same as `isDeviceSupported`, in favor of doing that mapping in Dart. - Eliminates `getEnrolledBiometrics` from the platform interface, since it was the same implementation as `isDeviceSupported` just with a different return value, in favor of doing that logic in Dart. - Moves throwing for the `biometricOnly` option to the Dart side, simplifying the native logic. Related changes: - Adds a significant amount of previously-missing Dart unit test coverage. - Removes the `biometricOnly` UI from the example app, since it will always fail. Part of https://github.com/flutter/flutter/issues/117912 --- .../local_auth_windows/CHANGELOG.md | 4 + .../local_auth_windows/example/lib/main.dart | 50 ------ .../lib/local_auth_windows.dart | 66 +++---- .../lib/src/messages.g.dart | 81 +++++++++ .../local_auth_windows/pigeons/copyright.txt | 3 + .../local_auth_windows/pigeons/messages.dart | 27 +++ .../local_auth_windows/pubspec.yaml | 3 +- .../test/local_auth_test.dart | 161 +++++++++++------- .../local_auth_windows/windows/CMakeLists.txt | 4 +- .../local_auth_windows/windows/local_auth.h | 33 ++-- .../windows/local_auth_plugin.cpp | 111 +++--------- .../local_auth_windows/windows/messages.g.cpp | 110 ++++++++++++ .../local_auth_windows/windows/messages.g.h | 93 ++++++++++ .../windows/test/local_auth_plugin_test.cpp | 150 ++-------------- .../local_auth_windows/windows/test/mocks.h | 19 --- 15 files changed, 512 insertions(+), 403 deletions(-) create mode 100644 packages/local_auth/local_auth_windows/lib/src/messages.g.dart create mode 100644 packages/local_auth/local_auth_windows/pigeons/copyright.txt create mode 100644 packages/local_auth/local_auth_windows/pigeons/messages.dart create mode 100644 packages/local_auth/local_auth_windows/windows/messages.g.cpp create mode 100644 packages/local_auth/local_auth_windows/windows/messages.g.h diff --git a/packages/local_auth/local_auth_windows/CHANGELOG.md b/packages/local_auth/local_auth_windows/CHANGELOG.md index b4f2061f2c27..d7bc77d551e7 100644 --- a/packages/local_auth/local_auth_windows/CHANGELOG.md +++ b/packages/local_auth/local_auth_windows/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.0.5 + +* Switches internal implementation to Pigeon. + ## 1.0.4 * Updates imports for `prefer_relative_imports`. diff --git a/packages/local_auth/local_auth_windows/example/lib/main.dart b/packages/local_auth/local_auth_windows/example/lib/main.dart index 546b635b8eca..b173e5414396 100644 --- a/packages/local_auth/local_auth_windows/example/lib/main.dart +++ b/packages/local_auth/local_auth_windows/example/lib/main.dart @@ -108,44 +108,6 @@ class _MyAppState extends State { () => _authorized = authenticated ? 'Authorized' : 'Not Authorized'); } - Future _authenticateWithBiometrics() async { - bool authenticated = false; - try { - setState(() { - _isAuthenticating = true; - _authorized = 'Authenticating'; - }); - authenticated = await LocalAuthPlatform.instance.authenticate( - localizedReason: - 'Scan your fingerprint (or face or whatever) to authenticate', - authMessages: [const WindowsAuthMessages()], - options: const AuthenticationOptions( - stickyAuth: true, - biometricOnly: true, - ), - ); - setState(() { - _isAuthenticating = false; - _authorized = 'Authenticating'; - }); - } on PlatformException catch (e) { - print(e); - setState(() { - _isAuthenticating = false; - _authorized = 'Error - ${e.message}'; - }); - return; - } - if (!mounted) { - return; - } - - final String message = authenticated ? 'Authorized' : 'Not Authorized'; - setState(() { - _authorized = message; - }); - } - Future _cancelAuthentication() async { await LocalAuthPlatform.instance.stopAuthentication(); setState(() => _isAuthenticating = false); @@ -209,18 +171,6 @@ class _MyAppState extends State { ], ), ), - ElevatedButton( - onPressed: _authenticateWithBiometrics, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Text(_isAuthenticating - ? 'Cancel' - : 'Authenticate: biometrics only'), - const Icon(Icons.fingerprint), - ], - ), - ), ], ), ], diff --git a/packages/local_auth/local_auth_windows/lib/local_auth_windows.dart b/packages/local_auth/local_auth_windows/lib/local_auth_windows.dart index b373782c2187..9f918aab0585 100644 --- a/packages/local_auth/local_auth_windows/lib/local_auth_windows.dart +++ b/packages/local_auth/local_auth_windows/lib/local_auth_windows.dart @@ -2,20 +2,25 @@ // 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/foundation.dart'; import 'package:local_auth_platform_interface/local_auth_platform_interface.dart'; -import 'types/auth_messages_windows.dart'; + +import 'src/messages.g.dart'; export 'package:local_auth_platform_interface/types/auth_messages.dart'; export 'package:local_auth_platform_interface/types/auth_options.dart'; export 'package:local_auth_platform_interface/types/biometric_type.dart'; export 'package:local_auth_windows/types/auth_messages_windows.dart'; -const MethodChannel _channel = - MethodChannel('plugins.flutter.io/local_auth_windows'); - /// The implementation of [LocalAuthPlatform] for Windows. class LocalAuthWindows extends LocalAuthPlatform { + /// Creates a new plugin implementation instance. + LocalAuthWindows({ + @visibleForTesting LocalAuthApi? api, + }) : _api = api ?? LocalAuthApi(); + + final LocalAuthApi _api; + /// Registers this class as the default instance of [LocalAuthPlatform]. static void registerWith() { LocalAuthPlatform.instance = LocalAuthWindows(); @@ -28,55 +33,36 @@ class LocalAuthWindows extends LocalAuthPlatform { AuthenticationOptions options = const AuthenticationOptions(), }) async { assert(localizedReason.isNotEmpty); - final Map args = { - 'localizedReason': localizedReason, - 'useErrorDialogs': options.useErrorDialogs, - 'stickyAuth': options.stickyAuth, - 'sensitiveTransaction': options.sensitiveTransaction, - 'biometricOnly': options.biometricOnly, - }; - args.addAll(const WindowsAuthMessages().args); - for (final AuthMessages messages in authMessages) { - if (messages is WindowsAuthMessages) { - args.addAll(messages.args); - } + + if (options.biometricOnly) { + throw UnsupportedError( + "Windows doesn't support the biometricOnly parameter."); } - return (await _channel.invokeMethod('authenticate', args)) ?? false; + + return _api.authenticate(localizedReason); } @override Future deviceSupportsBiometrics() async { - return (await _channel.invokeMethod('deviceSupportsBiometrics')) ?? - false; + // Biometrics are supported on any supported device. + return isDeviceSupported(); } @override Future> getEnrolledBiometrics() async { - final List result = (await _channel.invokeListMethod( - 'getEnrolledBiometrics', - )) ?? - []; - final List biometrics = []; - for (final String value in result) { - switch (value) { - case 'weak': - biometrics.add(BiometricType.weak); - break; - case 'strong': - biometrics.add(BiometricType.strong); - break; - } + // Windows doesn't support querying specific biometric types. Since the + // OS considers this a strong authentication API, return weak+strong on + // any supported device. + if (await isDeviceSupported()) { + return [BiometricType.weak, BiometricType.strong]; } - return biometrics; + return []; } @override - Future isDeviceSupported() async => - (await _channel.invokeMethod('isDeviceSupported')) ?? false; + Future isDeviceSupported() async => _api.isDeviceSupported(); /// Always returns false as this method is not supported on Windows. @override - Future stopAuthentication() async { - return false; - } + Future stopAuthentication() async => false; } diff --git a/packages/local_auth/local_auth_windows/lib/src/messages.g.dart b/packages/local_auth/local_auth_windows/lib/src/messages.g.dart new file mode 100644 index 000000000000..312d1c0ba164 --- /dev/null +++ b/packages/local_auth/local_auth_windows/lib/src/messages.g.dart @@ -0,0 +1,81 @@ +// Copyright 2013 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. +// Autogenerated from Pigeon (v5.0.1), do not edit directly. +// See also: https://pub.dev/packages/pigeon +// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import +import 'dart:async'; +import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; + +import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; +import 'package:flutter/services.dart'; + +class LocalAuthApi { + /// Constructor for [LocalAuthApi]. The [binaryMessenger] named argument is + /// available for dependency injection. If it is left null, the default + /// BinaryMessenger will be used which routes to the host platform. + LocalAuthApi({BinaryMessenger? binaryMessenger}) + : _binaryMessenger = binaryMessenger; + final BinaryMessenger? _binaryMessenger; + + static const MessageCodec codec = StandardMessageCodec(); + + /// Returns true if this device supports authentication. + Future isDeviceSupported() async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.LocalAuthApi.isDeviceSupported', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = await channel.send(null) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else if (replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (replyList[0] as bool?)!; + } + } + + /// Attempts to authenticate the user with the provided [localizedReason] as + /// the user-facing explanation for the authorization request. + /// + /// Returns true if authorization succeeds, false if it is attempted but is + /// not successful, and an error if authorization could not be attempted. + Future authenticate(String arg_localizedReason) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.LocalAuthApi.authenticate', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send([arg_localizedReason]) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else if (replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (replyList[0] as bool?)!; + } + } +} diff --git a/packages/local_auth/local_auth_windows/pigeons/copyright.txt b/packages/local_auth/local_auth_windows/pigeons/copyright.txt new file mode 100644 index 000000000000..1236b63caf3a --- /dev/null +++ b/packages/local_auth/local_auth_windows/pigeons/copyright.txt @@ -0,0 +1,3 @@ +Copyright 2013 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. diff --git a/packages/local_auth/local_auth_windows/pigeons/messages.dart b/packages/local_auth/local_auth_windows/pigeons/messages.dart new file mode 100644 index 000000000000..683becdd61fb --- /dev/null +++ b/packages/local_auth/local_auth_windows/pigeons/messages.dart @@ -0,0 +1,27 @@ +// Copyright 2013 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. + +import 'package:pigeon/pigeon.dart'; + +@ConfigurePigeon(PigeonOptions( + dartOut: 'lib/src/messages.g.dart', + cppOptions: CppOptions(namespace: 'local_auth_windows'), + cppHeaderOut: 'windows/messages.g.h', + cppSourceOut: 'windows/messages.g.cpp', + copyrightHeader: 'pigeons/copyright.txt', +)) +@HostApi() +abstract class LocalAuthApi { + /// Returns true if this device supports authentication. + @async + bool isDeviceSupported(); + + /// Attempts to authenticate the user with the provided [localizedReason] as + /// the user-facing explanation for the authorization request. + /// + /// Returns true if authorization succeeds, false if it is attempted but is + /// not successful, and an error if authorization could not be attempted. + @async + bool authenticate(String localizedReason); +} diff --git a/packages/local_auth/local_auth_windows/pubspec.yaml b/packages/local_auth/local_auth_windows/pubspec.yaml index 9a2effed92ee..45671e1af9d0 100644 --- a/packages/local_auth/local_auth_windows/pubspec.yaml +++ b/packages/local_auth/local_auth_windows/pubspec.yaml @@ -2,7 +2,7 @@ name: local_auth_windows description: Windows implementation of the local_auth plugin. repository: https://github.com/flutter/plugins/tree/main/packages/local_auth/local_auth_windows issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+local_auth%22 -version: 1.0.4 +version: 1.0.5 environment: sdk: ">=2.14.0 <3.0.0" @@ -24,3 +24,4 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter + pigeon: ^5.0.1 diff --git a/packages/local_auth/local_auth_windows/test/local_auth_test.dart b/packages/local_auth/local_auth_windows/test/local_auth_test.dart index b11c19e7b339..917e7b1784b6 100644 --- a/packages/local_auth/local_auth_windows/test/local_auth_test.dart +++ b/packages/local_auth/local_auth_windows/test/local_auth_test.dart @@ -2,78 +2,123 @@ // 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:local_auth_windows/local_auth_windows.dart'; +import 'package:local_auth_windows/src/messages.g.dart'; void main() { - TestWidgetsFlutterBinding.ensureInitialized(); - group('authenticate', () { - const MethodChannel channel = MethodChannel( - 'plugins.flutter.io/local_auth_windows', - ); - - final List log = []; - late LocalAuthWindows localAuthentication; + late _FakeLocalAuthApi api; + late LocalAuthWindows plugin; setUp(() { - channel.setMockMethodCallHandler((MethodCall methodCall) { - log.add(methodCall); - switch (methodCall.method) { - case 'getEnrolledBiometrics': - return Future>.value(['weak', 'strong']); - default: - return Future.value(true); - } - }); - localAuthentication = LocalAuthWindows(); - log.clear(); + api = _FakeLocalAuthApi(); + plugin = LocalAuthWindows(api: api); }); - test('authenticate with no arguments passes expected defaults', () async { - await localAuthentication.authenticate( + test('authenticate handles success', () async { + api.returnValue = true; + + final bool result = await plugin.authenticate( authMessages: [const WindowsAuthMessages()], localizedReason: 'My localized reason'); - expect( - log, - [ - isMethodCall('authenticate', - arguments: { - 'localizedReason': 'My localized reason', - 'useErrorDialogs': true, - 'stickyAuth': false, - 'sensitiveTransaction': true, - 'biometricOnly': false, - }..addAll(const WindowsAuthMessages().args)), - ], - ); + + expect(result, true); + expect(api.passedReason, 'My localized reason'); + }); + + test('authenticate handles failure', () async { + api.returnValue = false; + + final bool result = await plugin.authenticate( + authMessages: [const WindowsAuthMessages()], + localizedReason: 'My localized reason'); + + expect(result, false); + expect(api.passedReason, 'My localized reason'); }); - test('authenticate passes all options.', () async { - await localAuthentication.authenticate( - authMessages: [const WindowsAuthMessages()], - localizedReason: 'My localized reason', - options: const AuthenticationOptions( - useErrorDialogs: false, - stickyAuth: true, - sensitiveTransaction: false, - biometricOnly: true, - ), - ); + test('authenticate throws for biometricOnly', () async { expect( - log, - [ - isMethodCall('authenticate', - arguments: { - 'localizedReason': 'My localized reason', - 'useErrorDialogs': false, - 'stickyAuth': true, - 'sensitiveTransaction': false, - 'biometricOnly': true, - }..addAll(const WindowsAuthMessages().args)), - ], - ); + plugin.authenticate( + authMessages: [const WindowsAuthMessages()], + localizedReason: 'My localized reason', + options: const AuthenticationOptions(biometricOnly: true)), + throwsA(isUnsupportedError)); + }); + + test('isDeviceSupported handles supported', () async { + api.returnValue = true; + + final bool result = await plugin.isDeviceSupported(); + + expect(result, true); + }); + + test('isDeviceSupported handles unsupported', () async { + api.returnValue = false; + + final bool result = await plugin.isDeviceSupported(); + + expect(result, false); + }); + + test('deviceSupportsBiometrics handles supported', () async { + api.returnValue = true; + + final bool result = await plugin.deviceSupportsBiometrics(); + + expect(result, true); + }); + + test('deviceSupportsBiometrics handles unsupported', () async { + api.returnValue = false; + + final bool result = await plugin.deviceSupportsBiometrics(); + + expect(result, false); + }); + + test('getEnrolledBiometrics returns expected values when supported', + () async { + api.returnValue = true; + + final List result = await plugin.getEnrolledBiometrics(); + + expect(result, [BiometricType.weak, BiometricType.strong]); + }); + + test('getEnrolledBiometrics returns nothing when unsupported', () async { + api.returnValue = false; + + final List result = await plugin.getEnrolledBiometrics(); + + expect(result, isEmpty); + }); + + test('stopAuthentication returns false', () async { + final bool result = await plugin.stopAuthentication(); + + expect(result, false); }); }); } + +class _FakeLocalAuthApi implements LocalAuthApi { + /// The return value for [isDeviceSupported] and [authenticate]. + bool returnValue = false; + + /// The argument that was passed to [authenticate]. + String? passedReason; + + @override + Future authenticate(String localizedReason) async { + passedReason = localizedReason; + return returnValue; + } + + @override + Future isDeviceSupported() async { + return returnValue; + } +} diff --git a/packages/local_auth/local_auth_windows/windows/CMakeLists.txt b/packages/local_auth/local_auth_windows/windows/CMakeLists.txt index bcf59bb827c7..9784aa5badd9 100644 --- a/packages/local_auth/local_auth_windows/windows/CMakeLists.txt +++ b/packages/local_auth/local_auth_windows/windows/CMakeLists.txt @@ -49,12 +49,14 @@ include_directories(BEFORE SYSTEM ${CMAKE_BINARY_DIR}/include) list(APPEND PLUGIN_SOURCES "local_auth_plugin.cpp" + "local_auth.h" + "messages.g.cpp" + "messages.g.h" ) add_library(${PLUGIN_NAME} SHARED "include/local_auth_windows/local_auth_plugin.h" "local_auth_windows.cpp" - "local_auth.h" ${PLUGIN_SOURCES} ) apply_standard_settings(${PLUGIN_NAME}) diff --git a/packages/local_auth/local_auth_windows/windows/local_auth.h b/packages/local_auth/local_auth_windows/windows/local_auth.h index 94b91f88345a..9cdc6efbcd15 100644 --- a/packages/local_auth/local_auth_windows/windows/local_auth.h +++ b/packages/local_auth/local_auth_windows/windows/local_auth.h @@ -10,8 +10,6 @@ #include #include -#include "include/local_auth_windows/local_auth_plugin.h" - // Include prior to C++/WinRT Headers #include #include @@ -23,6 +21,8 @@ #include #include +#include "messages.g.h" + namespace local_auth_windows { // Abstract class that is used to determine whether a user @@ -50,7 +50,7 @@ class UserConsentVerifier { UserConsentVerifier& operator=(const UserConsentVerifier&) = delete; }; -class LocalAuthPlugin : public flutter::Plugin { +class LocalAuthPlugin : public flutter::Plugin, public LocalAuthApi { public: static void RegisterWithRegistrar(flutter::PluginRegistrarWindows* registrar); @@ -62,28 +62,25 @@ class LocalAuthPlugin : public flutter::Plugin { // Exists for unit testing with mock implementations. LocalAuthPlugin(std::unique_ptr user_consent_verifier); - // Handles method calls from Dart on this plugin's channel. - void HandleMethodCall( - const flutter::MethodCall& method_call, - std::unique_ptr> result); - virtual ~LocalAuthPlugin(); + // LocalAuthApi: + void IsDeviceSupported( + std::function reply)> result) override; + void Authenticate(const std::string& localized_reason, + std::function reply)> result) override; + private: std::unique_ptr user_consent_verifier_; // Starts authentication process. - winrt::fire_and_forget Authenticate( - const flutter::MethodCall& method_call, - std::unique_ptr> result); - - // Returns enrolled biometric types available on device. - winrt::fire_and_forget GetEnrolledBiometrics( - std::unique_ptr> result); + winrt::fire_and_forget AuthenticateCoroutine( + const std::string& localized_reason, + std::function reply)> result); // Returns whether the system supports Windows Hello. - winrt::fire_and_forget IsDeviceSupported( - std::unique_ptr> result); + winrt::fire_and_forget IsDeviceSupportedCoroutine( + std::function reply)> result); }; -} // namespace local_auth_windows \ No newline at end of file +} // namespace local_auth_windows diff --git a/packages/local_auth/local_auth_windows/windows/local_auth_plugin.cpp b/packages/local_auth/local_auth_windows/windows/local_auth_plugin.cpp index 7a25abb53010..80fab37ee50d 100644 --- a/packages/local_auth/local_auth_windows/windows/local_auth_plugin.cpp +++ b/packages/local_auth/local_auth_windows/windows/local_auth_plugin.cpp @@ -4,24 +4,10 @@ #include #include "local_auth.h" +#include "messages.g.h" namespace { -template -// Helper method for getting an argument from an EncodableValue. -T GetArgument(const std::string arg, const flutter::EncodableValue* args, - T fallback) { - T result{fallback}; - const auto* arguments = std::get_if(args); - if (arguments) { - auto result_it = arguments->find(flutter::EncodableValue(arg)); - if (result_it != arguments->end()) { - result = std::get(result_it->second); - } - } - return result; -} - // Returns the window's HWND for a given FlutterView. HWND GetRootWindow(flutter::FlutterView* view) { return ::GetAncestor(view->GetNativeWindow(), GA_ROOT); @@ -110,19 +96,9 @@ class UserConsentVerifierImpl : public UserConsentVerifier { // static void LocalAuthPlugin::RegisterWithRegistrar( flutter::PluginRegistrarWindows* registrar) { - auto channel = - std::make_unique>( - registrar->messenger(), "plugins.flutter.io/local_auth_windows", - &flutter::StandardMethodCodec::GetInstance()); - auto plugin = std::make_unique( [registrar]() { return GetRootWindow(registrar->GetView()); }); - - channel->SetMethodCallHandler( - [plugin_pointer = plugin.get()](const auto& call, auto result) { - plugin_pointer->HandleMethodCall(call, std::move(result)); - }); - + LocalAuthApi::SetUp(registrar->messenger(), plugin.get()); registrar->AddPlugin(std::move(plugin)); } @@ -137,36 +113,22 @@ LocalAuthPlugin::LocalAuthPlugin( LocalAuthPlugin::~LocalAuthPlugin() {} -void LocalAuthPlugin::HandleMethodCall( - const flutter::MethodCall& method_call, - std::unique_ptr> result) { - if (method_call.method_name().compare("authenticate") == 0) { - Authenticate(method_call, std::move(result)); - } else if (method_call.method_name().compare("getEnrolledBiometrics") == 0) { - GetEnrolledBiometrics(std::move(result)); - } else if (method_call.method_name().compare("isDeviceSupported") == 0 || - method_call.method_name().compare("deviceSupportsBiometrics") == - 0) { - IsDeviceSupported(std::move(result)); - } else { - result->NotImplemented(); - } +void LocalAuthPlugin::IsDeviceSupported( + std::function reply)> result) { + IsDeviceSupportedCoroutine(std::move(result)); +} + +void LocalAuthPlugin::Authenticate( + const std::string& localized_reason, + std::function reply)> result) { + AuthenticateCoroutine(localized_reason, std::move(result)); } // Starts authentication process. -winrt::fire_and_forget LocalAuthPlugin::Authenticate( - const flutter::MethodCall& method_call, - std::unique_ptr> result) { - std::wstring reason = Utf16FromUtf8(GetArgument( - "localizedReason", method_call.arguments(), std::string())); - - bool biometric_only = - GetArgument("biometricOnly", method_call.arguments(), false); - if (biometric_only) { - result->Error("biometricOnlyNotSupported", - "Windows doesn't support the biometricOnly parameter."); - co_return; - } +winrt::fire_and_forget LocalAuthPlugin::AuthenticateCoroutine( + const std::string& localized_reason, + std::function reply)> result) { + std::wstring reason = Utf16FromUtf8(localized_reason); winrt::Windows::Security::Credentials::UI::UserConsentVerifierAvailability ucv_availability = @@ -175,17 +137,19 @@ winrt::fire_and_forget LocalAuthPlugin::Authenticate( if (ucv_availability == winrt::Windows::Security::Credentials::UI:: UserConsentVerifierAvailability::DeviceNotPresent) { - result->Error("NoHardware", "No biometric hardware found"); + result(FlutterError("NoHardware", "No biometric hardware found")); co_return; } else if (ucv_availability == winrt::Windows::Security::Credentials::UI:: UserConsentVerifierAvailability::NotConfiguredForUser) { - result->Error("NotEnrolled", "No biometrics enrolled on this device."); + result( + FlutterError("NotEnrolled", "No biometrics enrolled on this device.")); co_return; } else if (ucv_availability != winrt::Windows::Security::Credentials::UI:: UserConsentVerifierAvailability::Available) { - result->Error("NotAvailable", "Required security features not enabled"); + result( + FlutterError("NotAvailable", "Required security features not enabled")); co_return; } @@ -195,42 +159,21 @@ winrt::fire_and_forget LocalAuthPlugin::Authenticate( co_await user_consent_verifier_->RequestVerificationForWindowAsync( reason); - result->Success(flutter::EncodableValue( - consent_result == winrt::Windows::Security::Credentials::UI:: - UserConsentVerificationResult::Verified)); + result(consent_result == winrt::Windows::Security::Credentials::UI:: + UserConsentVerificationResult::Verified); } catch (...) { - result->Success(flutter::EncodableValue(false)); - } -} - -// Returns biometric types available on device. -winrt::fire_and_forget LocalAuthPlugin::GetEnrolledBiometrics( - std::unique_ptr> result) { - try { - flutter::EncodableList biometrics; - winrt::Windows::Security::Credentials::UI::UserConsentVerifierAvailability - ucv_availability = - co_await user_consent_verifier_->CheckAvailabilityAsync(); - if (ucv_availability == winrt::Windows::Security::Credentials::UI:: - UserConsentVerifierAvailability::Available) { - biometrics.push_back(flutter::EncodableValue("weak")); - biometrics.push_back(flutter::EncodableValue("strong")); - } - result->Success(biometrics); - } catch (const std::exception& e) { - result->Error("no_biometrics_available", e.what()); + result(false); } } // Returns whether the device supports Windows Hello or not. -winrt::fire_and_forget LocalAuthPlugin::IsDeviceSupported( - std::unique_ptr> result) { +winrt::fire_and_forget LocalAuthPlugin::IsDeviceSupportedCoroutine( + std::function reply)> result) { winrt::Windows::Security::Credentials::UI::UserConsentVerifierAvailability ucv_availability = co_await user_consent_verifier_->CheckAvailabilityAsync(); - result->Success(flutter::EncodableValue( - ucv_availability == winrt::Windows::Security::Credentials::UI:: - UserConsentVerifierAvailability::Available)); + result(ucv_availability == winrt::Windows::Security::Credentials::UI:: + UserConsentVerifierAvailability::Available); } } // namespace local_auth_windows diff --git a/packages/local_auth/local_auth_windows/windows/messages.g.cpp b/packages/local_auth/local_auth_windows/windows/messages.g.cpp new file mode 100644 index 000000000000..e44b17c6a38d --- /dev/null +++ b/packages/local_auth/local_auth_windows/windows/messages.g.cpp @@ -0,0 +1,110 @@ +// Copyright 2013 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. +// Autogenerated from Pigeon (v5.0.1), do not edit directly. +// See also: https://pub.dev/packages/pigeon + +#undef _HAS_EXCEPTIONS + +#include "messages.g.h" + +#include +#include +#include +#include + +#include +#include +#include + +namespace local_auth_windows { +/// The codec used by LocalAuthApi. +const flutter::StandardMessageCodec& LocalAuthApi::GetCodec() { + return flutter::StandardMessageCodec::GetInstance( + &flutter::StandardCodecSerializer::GetInstance()); +} + +// Sets up an instance of `LocalAuthApi` to handle messages through the +// `binary_messenger`. +void LocalAuthApi::SetUp(flutter::BinaryMessenger* binary_messenger, + LocalAuthApi* api) { + { + auto channel = + std::make_unique>( + binary_messenger, + "dev.flutter.pigeon.LocalAuthApi.isDeviceSupported", &GetCodec()); + if (api != nullptr) { + channel->SetMessageHandler( + [api](const flutter::EncodableValue& message, + const flutter::MessageReply& reply) { + try { + api->IsDeviceSupported([reply](ErrorOr&& output) { + if (output.has_error()) { + reply(WrapError(output.error())); + return; + } + flutter::EncodableList wrapped; + wrapped.push_back( + flutter::EncodableValue(std::move(output).TakeValue())); + reply(flutter::EncodableValue(std::move(wrapped))); + }); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel->SetMessageHandler(nullptr); + } + } + { + auto channel = + std::make_unique>( + binary_messenger, "dev.flutter.pigeon.LocalAuthApi.authenticate", + &GetCodec()); + if (api != nullptr) { + channel->SetMessageHandler( + [api](const flutter::EncodableValue& message, + const flutter::MessageReply& reply) { + try { + const auto& args = std::get(message); + const auto& encodable_localized_reason_arg = args.at(0); + if (encodable_localized_reason_arg.IsNull()) { + reply(WrapError("localized_reason_arg unexpectedly null.")); + return; + } + const auto& localized_reason_arg = + std::get(encodable_localized_reason_arg); + api->Authenticate( + localized_reason_arg, [reply](ErrorOr&& output) { + if (output.has_error()) { + reply(WrapError(output.error())); + return; + } + flutter::EncodableList wrapped; + wrapped.push_back( + flutter::EncodableValue(std::move(output).TakeValue())); + reply(flutter::EncodableValue(std::move(wrapped))); + }); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel->SetMessageHandler(nullptr); + } + } +} + +flutter::EncodableValue LocalAuthApi::WrapError( + std::string_view error_message) { + return flutter::EncodableValue(flutter::EncodableList{ + flutter::EncodableValue(std::string(error_message)), + flutter::EncodableValue("Error"), flutter::EncodableValue()}); +} +flutter::EncodableValue LocalAuthApi::WrapError(const FlutterError& error) { + return flutter::EncodableValue(flutter::EncodableList{ + flutter::EncodableValue(error.message()), + flutter::EncodableValue(error.code()), error.details()}); +} + +} // namespace local_auth_windows diff --git a/packages/local_auth/local_auth_windows/windows/messages.g.h b/packages/local_auth/local_auth_windows/windows/messages.g.h new file mode 100644 index 000000000000..2ceff7732c90 --- /dev/null +++ b/packages/local_auth/local_auth_windows/windows/messages.g.h @@ -0,0 +1,93 @@ +// Copyright 2013 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. +// Autogenerated from Pigeon (v5.0.1), do not edit directly. +// See also: https://pub.dev/packages/pigeon + +#ifndef PIGEON_LOCAL_AUTH_WINDOWS_H_ +#define PIGEON_LOCAL_AUTH_WINDOWS_H_ +#include +#include +#include +#include + +#include +#include +#include + +namespace local_auth_windows { + +// Generated class from Pigeon. + +class FlutterError { + public: + explicit FlutterError(const std::string& code) : code_(code) {} + explicit FlutterError(const std::string& code, const std::string& message) + : code_(code), message_(message) {} + explicit FlutterError(const std::string& code, const std::string& message, + const flutter::EncodableValue& details) + : code_(code), message_(message), details_(details) {} + + const std::string& code() const { return code_; } + const std::string& message() const { return message_; } + const flutter::EncodableValue& details() const { return details_; } + + private: + std::string code_; + std::string message_; + flutter::EncodableValue details_; +}; + +template +class ErrorOr { + public: + ErrorOr(const T& rhs) { new (&v_) T(rhs); } + ErrorOr(const T&& rhs) { v_ = std::move(rhs); } + ErrorOr(const FlutterError& rhs) { new (&v_) FlutterError(rhs); } + ErrorOr(const FlutterError&& rhs) { v_ = std::move(rhs); } + + bool has_error() const { return std::holds_alternative(v_); } + const T& value() const { return std::get(v_); }; + const FlutterError& error() const { return std::get(v_); }; + + private: + friend class LocalAuthApi; + ErrorOr() = default; + T TakeValue() && { return std::get(std::move(v_)); } + + std::variant v_; +}; + +// Generated interface from Pigeon that represents a handler of messages from +// Flutter. +class LocalAuthApi { + public: + LocalAuthApi(const LocalAuthApi&) = delete; + LocalAuthApi& operator=(const LocalAuthApi&) = delete; + virtual ~LocalAuthApi(){}; + // Returns true if this device supports authentication. + virtual void IsDeviceSupported( + std::function reply)> result) = 0; + // Attempts to authenticate the user with the provided [localizedReason] as + // the user-facing explanation for the authorization request. + // + // Returns true if authorization succeeds, false if it is attempted but is + // not successful, and an error if authorization could not be attempted. + virtual void Authenticate( + const std::string& localized_reason, + std::function reply)> result) = 0; + + // The codec used by LocalAuthApi. + static const flutter::StandardMessageCodec& GetCodec(); + // Sets up an instance of `LocalAuthApi` to handle messages through the + // `binary_messenger`. + static void SetUp(flutter::BinaryMessenger* binary_messenger, + LocalAuthApi* api); + static flutter::EncodableValue WrapError(std::string_view error_message); + static flutter::EncodableValue WrapError(const FlutterError& error); + + protected: + LocalAuthApi() = default; +}; +} // namespace local_auth_windows +#endif // PIGEON_LOCAL_AUTH_WINDOWS_H_ diff --git a/packages/local_auth/local_auth_windows/windows/test/local_auth_plugin_test.cpp b/packages/local_auth/local_auth_windows/windows/test/local_auth_plugin_test.cpp index 3828b05eef07..6b1b0ed79c3f 100644 --- a/packages/local_auth/local_auth_windows/windows/test/local_auth_plugin_test.cpp +++ b/packages/local_auth/local_auth_windows/windows/test/local_auth_plugin_test.cpp @@ -4,10 +4,6 @@ #include "include/local_auth_windows/local_auth_plugin.h" -#include -#include -#include -#include #include #include #include @@ -32,9 +28,6 @@ using ::testing::Pointee; using ::testing::Return; TEST(LocalAuthPlugin, IsDeviceSupportedHandlerSuccessIfVerifierAvailable) { - std::unique_ptr result = - std::make_unique(); - std::unique_ptr mockConsentVerifier = std::make_unique(); @@ -48,48 +41,14 @@ TEST(LocalAuthPlugin, IsDeviceSupportedHandlerSuccessIfVerifierAvailable) { }); LocalAuthPlugin plugin(std::move(mockConsentVerifier)); + ErrorOr result(false); + plugin.IsDeviceSupported([&result](ErrorOr reply) { result = reply; }); - EXPECT_CALL(*result, ErrorInternal).Times(0); - EXPECT_CALL(*result, SuccessInternal(Pointee(EncodableValue(true)))); - - plugin.HandleMethodCall( - flutter::MethodCall("isDeviceSupported", - std::make_unique()), - std::move(result)); + EXPECT_FALSE(result.has_error()); + EXPECT_TRUE(result.value()); } TEST(LocalAuthPlugin, IsDeviceSupportedHandlerSuccessIfVerifierNotAvailable) { - std::unique_ptr result = - std::make_unique(); - - std::unique_ptr mockConsentVerifier = - std::make_unique(); - - EXPECT_CALL(*mockConsentVerifier, CheckAvailabilityAsync) - .Times(1) - .WillOnce([]() -> winrt::Windows::Foundation::IAsyncOperation< - winrt::Windows::Security::Credentials::UI:: - UserConsentVerifierAvailability> { - co_return winrt::Windows::Security::Credentials::UI:: - UserConsentVerifierAvailability::DeviceNotPresent; - }); - - LocalAuthPlugin plugin(std::move(mockConsentVerifier)); - - EXPECT_CALL(*result, ErrorInternal).Times(0); - EXPECT_CALL(*result, SuccessInternal(Pointee(EncodableValue(false)))); - - plugin.HandleMethodCall( - flutter::MethodCall("isDeviceSupported", - std::make_unique()), - std::move(result)); -} - -TEST(LocalAuthPlugin, - GetEnrolledBiometricsHandlerReturnEmptyListIfVerifierNotAvailable) { - std::unique_ptr result = - std::make_unique(); - std::unique_ptr mockConsentVerifier = std::make_unique(); @@ -103,72 +62,14 @@ TEST(LocalAuthPlugin, }); LocalAuthPlugin plugin(std::move(mockConsentVerifier)); + ErrorOr result(true); + plugin.IsDeviceSupported([&result](ErrorOr reply) { result = reply; }); - EXPECT_CALL(*result, ErrorInternal).Times(0); - EXPECT_CALL(*result, SuccessInternal(Pointee(EncodableList()))); - - plugin.HandleMethodCall( - flutter::MethodCall("getEnrolledBiometrics", - std::make_unique()), - std::move(result)); -} - -TEST(LocalAuthPlugin, - GetEnrolledBiometricsHandlerReturnNonEmptyListIfVerifierAvailable) { - std::unique_ptr result = - std::make_unique(); - - std::unique_ptr mockConsentVerifier = - std::make_unique(); - - EXPECT_CALL(*mockConsentVerifier, CheckAvailabilityAsync) - .Times(1) - .WillOnce([]() -> winrt::Windows::Foundation::IAsyncOperation< - winrt::Windows::Security::Credentials::UI:: - UserConsentVerifierAvailability> { - co_return winrt::Windows::Security::Credentials::UI:: - UserConsentVerifierAvailability::Available; - }); - - LocalAuthPlugin plugin(std::move(mockConsentVerifier)); - - EXPECT_CALL(*result, ErrorInternal).Times(0); - EXPECT_CALL(*result, - SuccessInternal(Pointee(EncodableList( - {EncodableValue("weak"), EncodableValue("strong")})))); - - plugin.HandleMethodCall( - flutter::MethodCall("getEnrolledBiometrics", - std::make_unique()), - std::move(result)); -} - -TEST(LocalAuthPlugin, AuthenticateHandlerDoesNotSupportBiometricOnly) { - std::unique_ptr result = - std::make_unique(); - - std::unique_ptr mockConsentVerifier = - std::make_unique(); - - LocalAuthPlugin plugin(std::move(mockConsentVerifier)); - - EXPECT_CALL(*result, ErrorInternal).Times(1); - EXPECT_CALL(*result, SuccessInternal).Times(0); - - std::unique_ptr args = - std::make_unique(EncodableMap({ - {"localizedReason", EncodableValue("My Reason")}, - {"biometricOnly", EncodableValue(true)}, - })); - - plugin.HandleMethodCall(flutter::MethodCall("authenticate", std::move(args)), - std::move(result)); + EXPECT_FALSE(result.has_error()); + EXPECT_FALSE(result.value()); } TEST(LocalAuthPlugin, AuthenticateHandlerWorksWhenAuthorized) { - std::unique_ptr result = - std::make_unique(); - std::unique_ptr mockConsentVerifier = std::make_unique(); @@ -193,24 +94,15 @@ TEST(LocalAuthPlugin, AuthenticateHandlerWorksWhenAuthorized) { }); LocalAuthPlugin plugin(std::move(mockConsentVerifier)); + ErrorOr result(false); + plugin.Authenticate("My Reason", + [&result](ErrorOr reply) { result = reply; }); - EXPECT_CALL(*result, ErrorInternal).Times(0); - EXPECT_CALL(*result, SuccessInternal(Pointee(EncodableValue(true)))); - - std::unique_ptr args = - std::make_unique(EncodableMap({ - {"localizedReason", EncodableValue("My Reason")}, - {"biometricOnly", EncodableValue(false)}, - })); - - plugin.HandleMethodCall(flutter::MethodCall("authenticate", std::move(args)), - std::move(result)); + EXPECT_FALSE(result.has_error()); + EXPECT_TRUE(result.value()); } TEST(LocalAuthPlugin, AuthenticateHandlerWorksWhenNotAuthorized) { - std::unique_ptr result = - std::make_unique(); - std::unique_ptr mockConsentVerifier = std::make_unique(); @@ -235,18 +127,12 @@ TEST(LocalAuthPlugin, AuthenticateHandlerWorksWhenNotAuthorized) { }); LocalAuthPlugin plugin(std::move(mockConsentVerifier)); + ErrorOr result(true); + plugin.Authenticate("My Reason", + [&result](ErrorOr reply) { result = reply; }); - EXPECT_CALL(*result, ErrorInternal).Times(0); - EXPECT_CALL(*result, SuccessInternal(Pointee(EncodableValue(false)))); - - std::unique_ptr args = - std::make_unique(EncodableMap({ - {"localizedReason", EncodableValue("My Reason")}, - {"biometricOnly", EncodableValue(false)}, - })); - - plugin.HandleMethodCall(flutter::MethodCall("authenticate", std::move(args)), - std::move(result)); + EXPECT_FALSE(result.has_error()); + EXPECT_FALSE(result.value()); } } // namespace test diff --git a/packages/local_auth/local_auth_windows/windows/test/mocks.h b/packages/local_auth/local_auth_windows/windows/test/mocks.h index d82ae801b4b9..a31eb98aa7ef 100644 --- a/packages/local_auth/local_auth_windows/windows/test/mocks.h +++ b/packages/local_auth/local_auth_windows/windows/test/mocks.h @@ -5,10 +5,6 @@ #ifndef PACKAGES_LOCAL_AUTH_LOCAL_AUTH_WINDOWS_WINDOWS_TEST_MOCKS_H_ #define PACKAGES_LOCAL_AUTH_LOCAL_AUTH_WINDOWS_WINDOWS_TEST_MOCKS_H_ -#include -#include -#include -#include #include #include @@ -19,23 +15,8 @@ namespace test { namespace { -using flutter::EncodableMap; -using flutter::EncodableValue; using ::testing::_; -class MockMethodResult : public flutter::MethodResult<> { - public: - ~MockMethodResult() = default; - - MOCK_METHOD(void, SuccessInternal, (const EncodableValue* result), - (override)); - MOCK_METHOD(void, ErrorInternal, - (const std::string& error_code, const std::string& error_message, - const EncodableValue* details), - (override)); - MOCK_METHOD(void, NotImplementedInternal, (), (override)); -}; - class MockUserConsentVerifier : public UserConsentVerifier { public: explicit MockUserConsentVerifier(){}; From 80e36c2e0a9f43fb2157d962f470071d81d1a48d Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Tue, 24 Jan 2023 14:23:00 -0500 Subject: [PATCH 030/130] Roll Flutter from bd7bee0f9eb8 to c35efdaa6854 (24 revisions) (#7017) * 3bf79607d [web] Fix paths fetched by flutter.js (flutter/flutter#118684) * e71e8daa2 76998e529 Roll Fuchsia Linux SDK from f613tOkDB282hW2tA... to GLRbnjiO5SbZKX-Us... (flutter/engine#39067) (flutter/flutter#119009) * 71a42563d Revert "[Re-land] Button padding m3 (#118640)" (flutter/flutter#118962) * 90ffb1c65 94fc0728f Roll Dart SDK from c52810968747 to 107a1280a61f (1 revision) (flutter/engine#39069) (flutter/flutter#119010) * 224e6aa18 Remove unnecessary null checks in flutter/gestures (flutter/flutter#118926) * 6cd494554 Remove unnecessary null checks in flutter_web_plugins (flutter/flutter#118862) * a63e19ba0 Remove unnecessary null checks in flutter_localizations (flutter/flutter#118863) * 19dfde698 Remove unnecessary null checks in `flutter/{foundation,services,physics}` (flutter/flutter#118910) * 392dffeb0 Update the Linux Android defines test to use dimensions when selecting a build bot (flutter/flutter#118930) * 5e50ed972 Test Utf8FromUtf16 (flutter/flutter#118647) * edb571e49 Update README.md (flutter/flutter#118803) * 38630b6bd Remove unnecessary null checks in `flutter_tool` (flutter/flutter#118857) * 332aed9c8 Revert "Update the Linux Android defines test to use dimensions when selecting a build bot (#118930)" (flutter/flutter#119023) * 84071aa2a Add todo for linux defines test. (flutter/flutter#119035) * e8b7f4b20 [examples] Fix typo in `refresh_indicator` example (flutter/flutter#119000) * df4420835 Remove ThemeData.buttonColor references (flutter/flutter#118658) * 65486163a Remove animated_complex_opacity_perf_macos__e2e_summary bringup (flutter/flutter#118916) * 59767e5fc Remove unnecessary null checks in `flutter/material` (flutter/flutter#119022) * 1906ce5d4 7d3233d26 [web] Build multiple CanvasKit variants (using toolchain_args) (flutter/engine#38448) (flutter/flutter#119021) * 720bea026 Remove unnecessary null checks in `flutter/widgets` (flutter/flutter#119028) * 0de8bef74 Remove unnecessary null checks in flutter/cupertino (flutter/flutter#119020) * 2e8dd9dd6 Run integration_ui_test_test_macos in prod (flutter/flutter#118919) * 64b4c69bc roll pub deps and remove archive, crypto, typed_data from allow-list (flutter/flutter#119018) * c35efdaa6 Remove superfluous words. (flutter/flutter#119008) --- .ci/flutter_master.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/flutter_master.version b/.ci/flutter_master.version index 1f9e5aac2b11..bd0f85100b71 100644 --- a/.ci/flutter_master.version +++ b/.ci/flutter_master.version @@ -1 +1 @@ -bd7bee0f9eb80625a5db1d8ffc35710dd283215f +c35efdaa6854330b28d3968f4ec8c073e5c49252 From dc631aca9c4140a01eb97a50cd182be553512bea Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Tue, 24 Jan 2023 15:17:35 -0500 Subject: [PATCH 031/130] Roll Flutter (stable) from 135454af3247 to b06b8b271095 (2551 revisions) (#7018) flutter/flutter@135454a...b06b8b2 --- .ci/flutter_stable.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/flutter_stable.version b/.ci/flutter_stable.version index f3933394e20a..45363bc9d5e9 100644 --- a/.ci/flutter_stable.version +++ b/.ci/flutter_stable.version @@ -1 +1 @@ -135454af32477f815a7525073027a3ff9eff1bfd +b06b8b2710955028a6b562f5aa6fe62941d6febf From cfc4e0ece89c50346da0f075bc3eaab7a0c964f6 Mon Sep 17 00:00:00 2001 From: stuartmorgan Date: Tue, 24 Jan 2023 17:19:35 -0800 Subject: [PATCH 032/130] [ci] Update legacy Flutter version tests (#7019) * Update the N-1 and N-2 test configs * Update all minumim versions * changelog updates --- .cirrus.yml | 4 ++-- packages/camera/camera_android/CHANGELOG.md | 4 ++++ packages/camera/camera_android/example/pubspec.yaml | 2 +- packages/camera/camera_android/pubspec.yaml | 2 +- packages/camera/camera_avfoundation/CHANGELOG.md | 4 ++++ packages/camera/camera_avfoundation/example/pubspec.yaml | 2 +- packages/camera/camera_avfoundation/pubspec.yaml | 2 +- packages/camera/camera_platform_interface/CHANGELOG.md | 4 ++++ packages/camera/camera_platform_interface/pubspec.yaml | 2 +- packages/camera/camera_web/CHANGELOG.md | 4 ++++ packages/camera/camera_web/example/pubspec.yaml | 2 +- packages/camera/camera_web/pubspec.yaml | 2 +- packages/camera/camera_windows/CHANGELOG.md | 4 ++++ packages/camera/camera_windows/example/pubspec.yaml | 2 +- packages/camera/camera_windows/pubspec.yaml | 2 +- packages/espresso/CHANGELOG.md | 4 ++++ packages/espresso/example/pubspec.yaml | 2 +- packages/espresso/pubspec.yaml | 2 +- packages/file_selector/file_selector/CHANGELOG.md | 4 ++++ packages/file_selector/file_selector/pubspec.yaml | 2 +- packages/file_selector/file_selector_ios/CHANGELOG.md | 4 ++++ packages/file_selector/file_selector_ios/pubspec.yaml | 2 +- packages/file_selector/file_selector_linux/CHANGELOG.md | 4 ++++ packages/file_selector/file_selector_linux/pubspec.yaml | 2 +- packages/file_selector/file_selector_macos/CHANGELOG.md | 4 ++++ .../file_selector/file_selector_macos/example/pubspec.yaml | 2 +- packages/file_selector/file_selector_macos/pubspec.yaml | 2 +- .../file_selector_platform_interface/CHANGELOG.md | 4 ++++ .../file_selector_platform_interface/pubspec.yaml | 2 +- packages/file_selector/file_selector_web/CHANGELOG.md | 4 ++++ packages/file_selector/file_selector_web/example/pubspec.yaml | 2 +- packages/file_selector/file_selector_web/pubspec.yaml | 2 +- packages/file_selector/file_selector_windows/CHANGELOG.md | 4 ++++ .../file_selector/file_selector_windows/example/pubspec.yaml | 2 +- packages/file_selector/file_selector_windows/pubspec.yaml | 2 +- packages/flutter_plugin_android_lifecycle/CHANGELOG.md | 2 +- packages/flutter_plugin_android_lifecycle/pubspec.yaml | 2 +- packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md | 4 ++++ .../google_maps_flutter/example/pubspec.yaml | 2 +- packages/google_maps_flutter/google_maps_flutter/pubspec.yaml | 2 +- .../google_maps_flutter_android/CHANGELOG.md | 4 ++++ .../google_maps_flutter_android/example/pubspec.yaml | 2 +- .../google_maps_flutter/google_maps_flutter_ios/CHANGELOG.md | 4 ++++ .../google_maps_flutter_ios/example/pubspec.yaml | 2 +- .../google_maps_flutter/google_maps_flutter_ios/pubspec.yaml | 2 +- .../google_maps_flutter_platform_interface/CHANGELOG.md | 4 ++++ .../google_maps_flutter_platform_interface/pubspec.yaml | 2 +- .../google_maps_flutter/google_maps_flutter_web/CHANGELOG.md | 4 ++++ .../google_maps_flutter_web/example/pubspec.yaml | 2 +- .../google_maps_flutter/google_maps_flutter_web/pubspec.yaml | 2 +- packages/google_sign_in/google_sign_in/CHANGELOG.md | 4 ++++ packages/google_sign_in/google_sign_in/example/pubspec.yaml | 2 +- packages/google_sign_in/google_sign_in/pubspec.yaml | 2 +- packages/google_sign_in/google_sign_in_android/CHANGELOG.md | 4 ++++ .../google_sign_in_android/example/pubspec.yaml | 2 +- packages/google_sign_in/google_sign_in_android/pubspec.yaml | 2 +- packages/google_sign_in/google_sign_in_ios/CHANGELOG.md | 4 ++++ .../google_sign_in/google_sign_in_ios/example/pubspec.yaml | 2 +- packages/google_sign_in/google_sign_in_ios/pubspec.yaml | 2 +- .../google_sign_in_platform_interface/CHANGELOG.md | 2 +- .../google_sign_in_platform_interface/pubspec.yaml | 2 +- packages/google_sign_in/google_sign_in_web/CHANGELOG.md | 4 ++++ .../google_sign_in/google_sign_in_web/example/pubspec.yaml | 2 +- packages/google_sign_in/google_sign_in_web/pubspec.yaml | 2 +- packages/image_picker/image_picker/CHANGELOG.md | 4 ++++ packages/image_picker/image_picker/example/pubspec.yaml | 2 +- packages/image_picker/image_picker/pubspec.yaml | 2 +- packages/image_picker/image_picker_android/CHANGELOG.md | 4 ++++ .../image_picker/image_picker_android/example/pubspec.yaml | 2 +- packages/image_picker/image_picker_android/pubspec.yaml | 2 +- packages/image_picker/image_picker_for_web/CHANGELOG.md | 4 ++++ .../image_picker/image_picker_for_web/example/pubspec.yaml | 2 +- packages/image_picker/image_picker_for_web/pubspec.yaml | 2 +- packages/image_picker/image_picker_ios/CHANGELOG.md | 4 ++++ packages/image_picker/image_picker_ios/example/pubspec.yaml | 2 +- packages/image_picker/image_picker_ios/pubspec.yaml | 2 +- .../image_picker/image_picker_platform_interface/CHANGELOG.md | 4 ++++ .../image_picker/image_picker_platform_interface/pubspec.yaml | 2 +- packages/image_picker/image_picker_windows/CHANGELOG.md | 4 ++++ .../image_picker/image_picker_windows/example/pubspec.yaml | 2 +- packages/image_picker/image_picker_windows/pubspec.yaml | 2 +- packages/in_app_purchase/in_app_purchase/CHANGELOG.md | 4 ++++ packages/in_app_purchase/in_app_purchase/example/pubspec.yaml | 2 +- packages/in_app_purchase/in_app_purchase/pubspec.yaml | 2 +- packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md | 4 ++++ .../in_app_purchase_android/example/pubspec.yaml | 2 +- packages/in_app_purchase/in_app_purchase_android/pubspec.yaml | 2 +- .../in_app_purchase_platform_interface/CHANGELOG.md | 4 ++++ .../in_app_purchase_platform_interface/pubspec.yaml | 2 +- .../in_app_purchase/in_app_purchase_storekit/CHANGELOG.md | 4 ++++ .../in_app_purchase_storekit/example/pubspec.yaml | 2 +- .../in_app_purchase/in_app_purchase_storekit/pubspec.yaml | 2 +- packages/ios_platform_images/CHANGELOG.md | 4 ++++ packages/ios_platform_images/example/pubspec.yaml | 2 +- packages/local_auth/local_auth/CHANGELOG.md | 4 ++++ packages/local_auth/local_auth/example/pubspec.yaml | 2 +- packages/local_auth/local_auth/pubspec.yaml | 2 +- packages/local_auth/local_auth_android/CHANGELOG.md | 4 ++++ packages/local_auth/local_auth_android/example/pubspec.yaml | 2 +- packages/local_auth/local_auth_android/pubspec.yaml | 2 +- packages/local_auth/local_auth_ios/CHANGELOG.md | 4 ++++ packages/local_auth/local_auth_ios/example/pubspec.yaml | 2 +- packages/local_auth/local_auth_ios/pubspec.yaml | 2 +- .../local_auth/local_auth_platform_interface/CHANGELOG.md | 4 ++++ .../local_auth/local_auth_platform_interface/pubspec.yaml | 2 +- packages/local_auth/local_auth_windows/CHANGELOG.md | 4 ++++ packages/local_auth/local_auth_windows/example/pubspec.yaml | 2 +- packages/local_auth/local_auth_windows/pubspec.yaml | 2 +- packages/path_provider/path_provider/CHANGELOG.md | 4 ++++ packages/path_provider/path_provider/example/pubspec.yaml | 2 +- packages/path_provider/path_provider/pubspec.yaml | 2 +- packages/path_provider/path_provider_android/CHANGELOG.md | 4 ++++ .../path_provider/path_provider_android/example/pubspec.yaml | 2 +- packages/path_provider/path_provider_android/pubspec.yaml | 2 +- packages/path_provider/path_provider_foundation/CHANGELOG.md | 4 ++++ .../path_provider_foundation/example/pubspec.yaml | 2 +- packages/path_provider/path_provider_foundation/pubspec.yaml | 2 +- packages/path_provider/path_provider_linux/CHANGELOG.md | 2 +- .../path_provider/path_provider_linux/example/pubspec.yaml | 2 +- packages/path_provider/path_provider_linux/pubspec.yaml | 2 +- .../path_provider_platform_interface/CHANGELOG.md | 4 ++++ .../path_provider_platform_interface/pubspec.yaml | 2 +- packages/path_provider/path_provider_windows/CHANGELOG.md | 4 ++++ .../path_provider/path_provider_windows/example/pubspec.yaml | 2 +- packages/quick_actions/quick_actions/CHANGELOG.md | 4 ++++ packages/quick_actions/quick_actions/example/pubspec.yaml | 2 +- packages/quick_actions/quick_actions/pubspec.yaml | 2 +- packages/quick_actions/quick_actions_android/CHANGELOG.md | 4 ++++ .../quick_actions/quick_actions_android/example/pubspec.yaml | 2 +- packages/quick_actions/quick_actions_android/pubspec.yaml | 2 +- packages/quick_actions/quick_actions_ios/CHANGELOG.md | 4 ++++ packages/quick_actions/quick_actions_ios/example/pubspec.yaml | 2 +- packages/quick_actions/quick_actions_ios/pubspec.yaml | 2 +- .../quick_actions_platform_interface/CHANGELOG.md | 4 ++++ .../quick_actions_platform_interface/pubspec.yaml | 2 +- packages/shared_preferences/shared_preferences/CHANGELOG.md | 4 ++++ .../shared_preferences/example/pubspec.yaml | 2 +- packages/shared_preferences/shared_preferences/pubspec.yaml | 2 +- .../shared_preferences_android/CHANGELOG.md | 4 ++++ .../shared_preferences_android/example/pubspec.yaml | 2 +- .../shared_preferences_android/pubspec.yaml | 2 +- .../shared_preferences_foundation/CHANGELOG.md | 4 ++++ .../shared_preferences_foundation/example/pubspec.yaml | 2 +- .../shared_preferences_foundation/pubspec.yaml | 2 +- .../shared_preferences/shared_preferences_linux/CHANGELOG.md | 4 ++++ .../shared_preferences_linux/example/pubspec.yaml | 2 +- .../shared_preferences/shared_preferences_linux/pubspec.yaml | 2 +- .../shared_preferences_platform_interface/CHANGELOG.md | 2 +- .../shared_preferences_platform_interface/pubspec.yaml | 2 +- .../shared_preferences/shared_preferences_web/CHANGELOG.md | 2 +- .../shared_preferences_web/example/pubspec.yaml | 2 +- .../shared_preferences/shared_preferences_web/pubspec.yaml | 2 +- .../shared_preferences_windows/CHANGELOG.md | 4 ++++ .../shared_preferences_windows/example/pubspec.yaml | 2 +- .../shared_preferences_windows/pubspec.yaml | 2 +- packages/url_launcher/url_launcher/CHANGELOG.md | 4 ++++ packages/url_launcher/url_launcher/example/pubspec.yaml | 2 +- packages/url_launcher/url_launcher/pubspec.yaml | 2 +- packages/url_launcher/url_launcher_android/CHANGELOG.md | 4 ++++ .../url_launcher/url_launcher_android/example/pubspec.yaml | 2 +- packages/url_launcher/url_launcher_android/pubspec.yaml | 2 +- packages/url_launcher/url_launcher_ios/CHANGELOG.md | 4 ++++ packages/url_launcher/url_launcher_ios/example/pubspec.yaml | 2 +- packages/url_launcher/url_launcher_ios/pubspec.yaml | 2 +- packages/url_launcher/url_launcher_linux/CHANGELOG.md | 4 ++++ packages/url_launcher/url_launcher_linux/example/pubspec.yaml | 2 +- packages/url_launcher/url_launcher_linux/pubspec.yaml | 2 +- packages/url_launcher/url_launcher_macos/CHANGELOG.md | 4 ++++ packages/url_launcher/url_launcher_macos/example/pubspec.yaml | 2 +- packages/url_launcher/url_launcher_macos/pubspec.yaml | 2 +- .../url_launcher/url_launcher_platform_interface/CHANGELOG.md | 4 ++++ .../url_launcher/url_launcher_platform_interface/pubspec.yaml | 2 +- packages/url_launcher/url_launcher_web/CHANGELOG.md | 4 ++++ packages/url_launcher/url_launcher_web/example/pubspec.yaml | 2 +- packages/url_launcher/url_launcher_web/pubspec.yaml | 2 +- packages/url_launcher/url_launcher_windows/CHANGELOG.md | 4 ++++ .../url_launcher/url_launcher_windows/example/pubspec.yaml | 2 +- packages/url_launcher/url_launcher_windows/pubspec.yaml | 2 +- packages/video_player/video_player/CHANGELOG.md | 4 ++++ packages/video_player/video_player/example/pubspec.yaml | 2 +- packages/video_player/video_player/pubspec.yaml | 2 +- packages/video_player/video_player_android/CHANGELOG.md | 4 ++++ .../video_player/video_player_android/example/pubspec.yaml | 2 +- packages/video_player/video_player_android/pubspec.yaml | 2 +- packages/video_player/video_player_avfoundation/CHANGELOG.md | 4 ++++ .../video_player_avfoundation/example/pubspec.yaml | 2 +- packages/video_player/video_player_avfoundation/pubspec.yaml | 2 +- .../video_player/video_player_platform_interface/CHANGELOG.md | 4 ++++ .../video_player/video_player_platform_interface/pubspec.yaml | 2 +- packages/video_player/video_player_web/CHANGELOG.md | 4 ++++ packages/video_player/video_player_web/example/pubspec.yaml | 2 +- packages/video_player/video_player_web/pubspec.yaml | 2 +- .../webview_flutter_platform_interface/CHANGELOG.md | 4 ++++ .../webview_flutter_platform_interface/pubspec.yaml | 2 +- packages/webview_flutter/webview_flutter_web/CHANGELOG.md | 4 ++++ packages/webview_flutter/webview_flutter_web/pubspec.yaml | 2 +- 196 files changed, 398 insertions(+), 130 deletions(-) diff --git a/.cirrus.yml b/.cirrus.yml index 5f3c5fe8b39a..85f8a2a115dd 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -175,8 +175,8 @@ task: CHANNEL: "3.0.5" DART_VERSION: "2.17.6" env: - CHANNEL: "2.10.5" - DART_VERSION: "2.16.2" + CHANNEL: "3.3.10" + DART_VERSION: "2.18.6" package_prep_script: # Allow analyzing packages that use a dev dependency with a higher # minimum Flutter/Dart version than the package itself. diff --git a/packages/camera/camera_android/CHANGELOG.md b/packages/camera/camera_android/CHANGELOG.md index 7174cf4383a5..0aec0be2b442 100644 --- a/packages/camera/camera_android/CHANGELOG.md +++ b/packages/camera/camera_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 0.10.2+3 * Updates code for stricter lint checks. diff --git a/packages/camera/camera_android/example/pubspec.yaml b/packages/camera/camera_android/example/pubspec.yaml index 8c985d94fd5a..e23e31a886de 100644 --- a/packages/camera/camera_android/example/pubspec.yaml +++ b/packages/camera/camera_android/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: camera_android: diff --git a/packages/camera/camera_android/pubspec.yaml b/packages/camera/camera_android/pubspec.yaml index d61642faa387..30d6153cece6 100644 --- a/packages/camera/camera_android/pubspec.yaml +++ b/packages/camera/camera_android/pubspec.yaml @@ -6,7 +6,7 @@ version: 0.10.2+3 environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/camera/camera_avfoundation/CHANGELOG.md b/packages/camera/camera_avfoundation/CHANGELOG.md index 6cc6b1bd005c..caa69b6296b8 100644 --- a/packages/camera/camera_avfoundation/CHANGELOG.md +++ b/packages/camera/camera_avfoundation/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 0.9.10+2 * Updates code for stricter lint checks. diff --git a/packages/camera/camera_avfoundation/example/pubspec.yaml b/packages/camera/camera_avfoundation/example/pubspec.yaml index a9252cbd6d61..7c85ba807193 100644 --- a/packages/camera/camera_avfoundation/example/pubspec.yaml +++ b/packages/camera/camera_avfoundation/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: camera_avfoundation: diff --git a/packages/camera/camera_avfoundation/pubspec.yaml b/packages/camera/camera_avfoundation/pubspec.yaml index 94a700240308..9461d934a81d 100644 --- a/packages/camera/camera_avfoundation/pubspec.yaml +++ b/packages/camera/camera_avfoundation/pubspec.yaml @@ -6,7 +6,7 @@ version: 0.9.10+2 environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/camera/camera_platform_interface/CHANGELOG.md b/packages/camera/camera_platform_interface/CHANGELOG.md index ba27eb3c7fe6..64fb555a99de 100644 --- a/packages/camera/camera_platform_interface/CHANGELOG.md +++ b/packages/camera/camera_platform_interface/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 2.3.4 * Updates code for stricter lint checks. diff --git a/packages/camera/camera_platform_interface/pubspec.yaml b/packages/camera/camera_platform_interface/pubspec.yaml index 50ce9bee8531..ff024c0404ad 100644 --- a/packages/camera/camera_platform_interface/pubspec.yaml +++ b/packages/camera/camera_platform_interface/pubspec.yaml @@ -8,7 +8,7 @@ version: 2.3.4 environment: sdk: '>=2.12.0 <3.0.0' - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: cross_file: ^0.3.1 diff --git a/packages/camera/camera_web/CHANGELOG.md b/packages/camera/camera_web/CHANGELOG.md index eb19a54b6555..2a8d43b95e18 100644 --- a/packages/camera/camera_web/CHANGELOG.md +++ b/packages/camera/camera_web/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 0.3.1+1 * Updates code for stricter lint checks. diff --git a/packages/camera/camera_web/example/pubspec.yaml b/packages/camera/camera_web/example/pubspec.yaml index e82bbe392ceb..ee66870c051d 100644 --- a/packages/camera/camera_web/example/pubspec.yaml +++ b/packages/camera/camera_web/example/pubspec.yaml @@ -3,7 +3,7 @@ publish_to: none environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/camera/camera_web/pubspec.yaml b/packages/camera/camera_web/pubspec.yaml index b5b19fc487ac..101444b98fe4 100644 --- a/packages/camera/camera_web/pubspec.yaml +++ b/packages/camera/camera_web/pubspec.yaml @@ -6,7 +6,7 @@ version: 0.3.1+1 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/camera/camera_windows/CHANGELOG.md b/packages/camera/camera_windows/CHANGELOG.md index a6269b955983..34ee66815aa6 100644 --- a/packages/camera/camera_windows/CHANGELOG.md +++ b/packages/camera/camera_windows/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 0.2.1+4 * Updates code for stricter lint checks. diff --git a/packages/camera/camera_windows/example/pubspec.yaml b/packages/camera/camera_windows/example/pubspec.yaml index 80ce958a0e84..69ce1c330156 100644 --- a/packages/camera/camera_windows/example/pubspec.yaml +++ b/packages/camera/camera_windows/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: 'none' environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: camera_platform_interface: ^2.1.2 diff --git a/packages/camera/camera_windows/pubspec.yaml b/packages/camera/camera_windows/pubspec.yaml index d87491a6c0cf..e028559c28ab 100644 --- a/packages/camera/camera_windows/pubspec.yaml +++ b/packages/camera/camera_windows/pubspec.yaml @@ -6,7 +6,7 @@ version: 0.2.1+4 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/espresso/CHANGELOG.md b/packages/espresso/CHANGELOG.md index d2652dc64ef7..709bd1925bbf 100644 --- a/packages/espresso/CHANGELOG.md +++ b/packages/espresso/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 0.2.0+6 * Updates espresso-accessibility to 3.5.1. diff --git a/packages/espresso/example/pubspec.yaml b/packages/espresso/example/pubspec.yaml index 67f9edcd4644..0adf623b728a 100644 --- a/packages/espresso/example/pubspec.yaml +++ b/packages/espresso/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/espresso/pubspec.yaml b/packages/espresso/pubspec.yaml index 984dd70e4318..be018c9f8274 100644 --- a/packages/espresso/pubspec.yaml +++ b/packages/espresso/pubspec.yaml @@ -7,7 +7,7 @@ version: 0.2.0+6 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/file_selector/file_selector/CHANGELOG.md b/packages/file_selector/file_selector/CHANGELOG.md index 7983aa57561f..b8784e518c5a 100644 --- a/packages/file_selector/file_selector/CHANGELOG.md +++ b/packages/file_selector/file_selector/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 0.9.2+2 * Improves API docs and examples. diff --git a/packages/file_selector/file_selector/pubspec.yaml b/packages/file_selector/file_selector/pubspec.yaml index ad187d6f446a..17e41cd656dd 100644 --- a/packages/file_selector/file_selector/pubspec.yaml +++ b/packages/file_selector/file_selector/pubspec.yaml @@ -7,7 +7,7 @@ version: 0.9.2+2 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/file_selector/file_selector_ios/CHANGELOG.md b/packages/file_selector/file_selector_ios/CHANGELOG.md index 439e1d4fd4c1..ccb51eff9ffd 100644 --- a/packages/file_selector/file_selector_ios/CHANGELOG.md +++ b/packages/file_selector/file_selector_ios/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 0.5.0+2 * Changes XTypeGroup initialization from final to const. diff --git a/packages/file_selector/file_selector_ios/pubspec.yaml b/packages/file_selector/file_selector_ios/pubspec.yaml index 3f8ecfac04ce..e772cb7d8632 100644 --- a/packages/file_selector/file_selector_ios/pubspec.yaml +++ b/packages/file_selector/file_selector_ios/pubspec.yaml @@ -6,7 +6,7 @@ version: 0.5.0+2 environment: sdk: ">=2.14.4 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/file_selector/file_selector_linux/CHANGELOG.md b/packages/file_selector/file_selector_linux/CHANGELOG.md index 70ce307180d8..00ccb06d7b38 100644 --- a/packages/file_selector/file_selector_linux/CHANGELOG.md +++ b/packages/file_selector/file_selector_linux/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 0.9.1 * Adds `getDirectoryPaths` implementation. diff --git a/packages/file_selector/file_selector_linux/pubspec.yaml b/packages/file_selector/file_selector_linux/pubspec.yaml index 5aff9493d28f..af88485b0ef2 100644 --- a/packages/file_selector/file_selector_linux/pubspec.yaml +++ b/packages/file_selector/file_selector_linux/pubspec.yaml @@ -6,7 +6,7 @@ version: 0.9.1 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/file_selector/file_selector_macos/CHANGELOG.md b/packages/file_selector/file_selector_macos/CHANGELOG.md index 27d08ae26771..d9da2eceeea9 100644 --- a/packages/file_selector/file_selector_macos/CHANGELOG.md +++ b/packages/file_selector/file_selector_macos/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 0.9.0+4 * Converts platform channel to Pigeon. diff --git a/packages/file_selector/file_selector_macos/example/pubspec.yaml b/packages/file_selector/file_selector_macos/example/pubspec.yaml index d3f3114bb481..a2122b2858b7 100644 --- a/packages/file_selector/file_selector_macos/example/pubspec.yaml +++ b/packages/file_selector/file_selector_macos/example/pubspec.yaml @@ -5,7 +5,7 @@ version: 1.0.0 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: file_selector_macos: diff --git a/packages/file_selector/file_selector_macos/pubspec.yaml b/packages/file_selector/file_selector_macos/pubspec.yaml index ac41a2525f4d..3654beaca4c0 100644 --- a/packages/file_selector/file_selector_macos/pubspec.yaml +++ b/packages/file_selector/file_selector_macos/pubspec.yaml @@ -6,7 +6,7 @@ version: 0.9.0+4 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/file_selector/file_selector_platform_interface/CHANGELOG.md b/packages/file_selector/file_selector_platform_interface/CHANGELOG.md index e0b08f086977..ae415ef8600d 100644 --- a/packages/file_selector/file_selector_platform_interface/CHANGELOG.md +++ b/packages/file_selector/file_selector_platform_interface/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 2.4.0 * Adds `getDirectoryPaths` method to the interface. diff --git a/packages/file_selector/file_selector_platform_interface/pubspec.yaml b/packages/file_selector/file_selector_platform_interface/pubspec.yaml index 4ab63acbf7e6..b2461ee2a6d0 100644 --- a/packages/file_selector/file_selector_platform_interface/pubspec.yaml +++ b/packages/file_selector/file_selector_platform_interface/pubspec.yaml @@ -8,7 +8,7 @@ version: 2.4.0 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: cross_file: ^0.3.0 diff --git a/packages/file_selector/file_selector_web/CHANGELOG.md b/packages/file_selector/file_selector_web/CHANGELOG.md index 5e531bb633d2..fbb58d61f999 100644 --- a/packages/file_selector/file_selector_web/CHANGELOG.md +++ b/packages/file_selector/file_selector_web/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 0.9.0+2 * Changes XTypeGroup initialization from final to const. diff --git a/packages/file_selector/file_selector_web/example/pubspec.yaml b/packages/file_selector/file_selector_web/example/pubspec.yaml index e14f5c2eedea..985ce35f69a8 100644 --- a/packages/file_selector/file_selector_web/example/pubspec.yaml +++ b/packages/file_selector/file_selector_web/example/pubspec.yaml @@ -3,7 +3,7 @@ publish_to: none environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: file_selector_platform_interface: ^2.2.0 diff --git a/packages/file_selector/file_selector_web/pubspec.yaml b/packages/file_selector/file_selector_web/pubspec.yaml index 848a41b754af..aceeb8b13693 100644 --- a/packages/file_selector/file_selector_web/pubspec.yaml +++ b/packages/file_selector/file_selector_web/pubspec.yaml @@ -6,7 +6,7 @@ version: 0.9.0+2 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/file_selector/file_selector_windows/CHANGELOG.md b/packages/file_selector/file_selector_windows/CHANGELOG.md index 13e895ca46f1..ee007d473d3d 100644 --- a/packages/file_selector/file_selector_windows/CHANGELOG.md +++ b/packages/file_selector/file_selector_windows/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 0.9.1+4 * Changes XTypeGroup initialization from final to const. diff --git a/packages/file_selector/file_selector_windows/example/pubspec.yaml b/packages/file_selector/file_selector_windows/example/pubspec.yaml index bc886d32c896..d270c3067325 100644 --- a/packages/file_selector/file_selector_windows/example/pubspec.yaml +++ b/packages/file_selector/file_selector_windows/example/pubspec.yaml @@ -5,7 +5,7 @@ version: 1.0.0 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: file_selector_platform_interface: ^2.2.0 diff --git a/packages/file_selector/file_selector_windows/pubspec.yaml b/packages/file_selector/file_selector_windows/pubspec.yaml index ee0701b3fd30..a0a0f39fbd1f 100644 --- a/packages/file_selector/file_selector_windows/pubspec.yaml +++ b/packages/file_selector/file_selector_windows/pubspec.yaml @@ -6,7 +6,7 @@ version: 0.9.1+4 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/flutter_plugin_android_lifecycle/CHANGELOG.md b/packages/flutter_plugin_android_lifecycle/CHANGELOG.md index 81202f8159de..c169487f6a81 100644 --- a/packages/flutter_plugin_android_lifecycle/CHANGELOG.md +++ b/packages/flutter_plugin_android_lifecycle/CHANGELOG.md @@ -1,6 +1,6 @@ ## NEXT -* Updates minimum Flutter version to 2.10. +* Updates minimum Flutter version to 3.0. ## 2.0.7 diff --git a/packages/flutter_plugin_android_lifecycle/pubspec.yaml b/packages/flutter_plugin_android_lifecycle/pubspec.yaml index 3a6a2e017b53..4711d1c3629a 100644 --- a/packages/flutter_plugin_android_lifecycle/pubspec.yaml +++ b/packages/flutter_plugin_android_lifecycle/pubspec.yaml @@ -6,7 +6,7 @@ version: 2.0.7 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md index 4287430c32ff..bab8412142d9 100644 --- a/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md +++ b/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 2.2.3 * Fixes a minor syntax error in `README.md`. diff --git a/packages/google_maps_flutter/google_maps_flutter/example/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter/example/pubspec.yaml index 06bfbbf290e4..5813d42e617e 100644 --- a/packages/google_maps_flutter/google_maps_flutter/example/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: cupertino_icons: ^1.0.5 diff --git a/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml index 9e20b486bb67..0771314b9e44 100644 --- a/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml @@ -6,7 +6,7 @@ version: 2.2.3 environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/google_maps_flutter/google_maps_flutter_android/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter_android/CHANGELOG.md index 8264c559e677..6e596c135f38 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/CHANGELOG.md +++ b/packages/google_maps_flutter/google_maps_flutter_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 2.4.3 * Updates code for stricter lint checks. diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_android/example/pubspec.yaml index a5e8bdb8bed6..aa29fa99a97b 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/example/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter_android/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: cupertino_icons: ^1.0.5 diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter_ios/CHANGELOG.md index fa135d8ff878..a65523f426c1 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/CHANGELOG.md +++ b/packages/google_maps_flutter/google_maps_flutter_ios/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 2.1.13 * Updates code for stricter lint checks. diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_ios/example/pubspec.yaml index 9c41fa57545e..ac27996fbc25 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/example/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: cupertino_icons: ^1.0.5 diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_ios/pubspec.yaml index 579b24304cf4..c4f8d23cb382 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter_ios/pubspec.yaml @@ -6,7 +6,7 @@ version: 2.1.13 environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter_platform_interface/CHANGELOG.md index 307b70016009..b3d6c5540e7a 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/CHANGELOG.md +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 2.2.5 * Updates code for stricter lint checks. diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_platform_interface/pubspec.yaml index 40b058ae6daf..6dfff89f8c4b 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/pubspec.yaml @@ -8,7 +8,7 @@ version: 2.2.5 environment: sdk: '>=2.12.0 <3.0.0' - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: collection: ^1.15.0 diff --git a/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md index 3e3ee7f65a04..42930348965f 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md +++ b/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 0.4.0+5 * Updates code for stricter lint checks. diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_web/example/pubspec.yaml index 35c9c903e982..43f67946464a 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/example/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter_web/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none # Tests require flutter beta or greater to run. environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/google_maps_flutter/google_maps_flutter_web/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_web/pubspec.yaml index e4d02b11eead..072d584b133f 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter_web/pubspec.yaml @@ -6,7 +6,7 @@ version: 0.4.0+5 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/google_sign_in/google_sign_in/CHANGELOG.md b/packages/google_sign_in/google_sign_in/CHANGELOG.md index c7ddb6ba9345..75769d4dbaa0 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 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 5.4.3 * Updates code for stricter lint checks. diff --git a/packages/google_sign_in/google_sign_in/example/pubspec.yaml b/packages/google_sign_in/google_sign_in/example/pubspec.yaml index fbf8f7cf0591..f1cd3828bd87 100644 --- a/packages/google_sign_in/google_sign_in/example/pubspec.yaml +++ b/packages/google_sign_in/google_sign_in/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/google_sign_in/google_sign_in/pubspec.yaml b/packages/google_sign_in/google_sign_in/pubspec.yaml index f6e1faf12089..d4f872f584d2 100644 --- a/packages/google_sign_in/google_sign_in/pubspec.yaml +++ b/packages/google_sign_in/google_sign_in/pubspec.yaml @@ -8,7 +8,7 @@ version: 5.4.3 environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/google_sign_in/google_sign_in_android/CHANGELOG.md b/packages/google_sign_in/google_sign_in_android/CHANGELOG.md index e03ca2dc7297..5d1b2d24d7d1 100644 --- a/packages/google_sign_in/google_sign_in_android/CHANGELOG.md +++ b/packages/google_sign_in/google_sign_in_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 6.1.5 * Updates play-services-auth version to 20.4.1. diff --git a/packages/google_sign_in/google_sign_in_android/example/pubspec.yaml b/packages/google_sign_in/google_sign_in_android/example/pubspec.yaml index 5ac2240cbba1..72d8b82a9bf5 100644 --- a/packages/google_sign_in/google_sign_in_android/example/pubspec.yaml +++ b/packages/google_sign_in/google_sign_in_android/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/google_sign_in/google_sign_in_android/pubspec.yaml b/packages/google_sign_in/google_sign_in_android/pubspec.yaml index ccf212dd3e71..b6b581a75764 100644 --- a/packages/google_sign_in/google_sign_in_android/pubspec.yaml +++ b/packages/google_sign_in/google_sign_in_android/pubspec.yaml @@ -6,7 +6,7 @@ version: 6.1.5 environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/google_sign_in/google_sign_in_ios/CHANGELOG.md b/packages/google_sign_in/google_sign_in_ios/CHANGELOG.md index 7c5ebc097568..495d268bde03 100644 --- a/packages/google_sign_in/google_sign_in_ios/CHANGELOG.md +++ b/packages/google_sign_in/google_sign_in_ios/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 5.5.1 * Fixes passing `serverClientId` via the channelled `init` call diff --git a/packages/google_sign_in/google_sign_in_ios/example/pubspec.yaml b/packages/google_sign_in/google_sign_in_ios/example/pubspec.yaml index aedc4b01aade..e2e643d1805d 100644 --- a/packages/google_sign_in/google_sign_in_ios/example/pubspec.yaml +++ b/packages/google_sign_in/google_sign_in_ios/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/google_sign_in/google_sign_in_ios/pubspec.yaml b/packages/google_sign_in/google_sign_in_ios/pubspec.yaml index 04998d8945b4..69884ca0fe70 100644 --- a/packages/google_sign_in/google_sign_in_ios/pubspec.yaml +++ b/packages/google_sign_in/google_sign_in_ios/pubspec.yaml @@ -6,7 +6,7 @@ version: 5.5.1 environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: 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 01d54b23dae0..8adba8aa966f 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,6 +1,6 @@ ## NEXT -* Updates minimum Flutter version to 2.10. +* Updates minimum Flutter version to 3.0. ## 2.3.0 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 0902069364ce..936257b9d817 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 @@ -8,7 +8,7 @@ version: 2.3.0 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: 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 c5c57992a997..85c46da8facc 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 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 0.10.2+1 * Updates code for `no_leading_underscores_for_local_identifiers` lint. diff --git a/packages/google_sign_in/google_sign_in_web/example/pubspec.yaml b/packages/google_sign_in/google_sign_in_web/example/pubspec.yaml index e5abdacf944d..848517534ed2 100644 --- a/packages/google_sign_in/google_sign_in_web/example/pubspec.yaml +++ b/packages/google_sign_in/google_sign_in_web/example/pubspec.yaml @@ -3,7 +3,7 @@ publish_to: none environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: 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 55bf760bcbf2..a9d39471c3ed 100644 --- a/packages/google_sign_in/google_sign_in_web/pubspec.yaml +++ b/packages/google_sign_in/google_sign_in_web/pubspec.yaml @@ -7,7 +7,7 @@ version: 0.10.2+1 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/image_picker/image_picker/CHANGELOG.md b/packages/image_picker/image_picker/CHANGELOG.md index 2974cfe4d26f..1f138ef26118 100644 --- a/packages/image_picker/image_picker/CHANGELOG.md +++ b/packages/image_picker/image_picker/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 0.8.6+1 * Updates code for stricter lint checks. diff --git a/packages/image_picker/image_picker/example/pubspec.yaml b/packages/image_picker/image_picker/example/pubspec.yaml index e9511e27ab6d..3d97877498dc 100755 --- a/packages/image_picker/image_picker/example/pubspec.yaml +++ b/packages/image_picker/image_picker/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/image_picker/image_picker/pubspec.yaml b/packages/image_picker/image_picker/pubspec.yaml index c2bf82278fa4..7bd8ecfd9b9b 100755 --- a/packages/image_picker/image_picker/pubspec.yaml +++ b/packages/image_picker/image_picker/pubspec.yaml @@ -7,7 +7,7 @@ version: 0.8.6+1 environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/image_picker/image_picker_android/CHANGELOG.md b/packages/image_picker/image_picker_android/CHANGELOG.md index 06d35d2f1a67..58c4951a2d4f 100644 --- a/packages/image_picker/image_picker_android/CHANGELOG.md +++ b/packages/image_picker/image_picker_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 0.8.5+5 * Updates code for stricter lint checks. diff --git a/packages/image_picker/image_picker_android/example/pubspec.yaml b/packages/image_picker/image_picker_android/example/pubspec.yaml index 02ef8a02af4c..bfcdbad511ae 100755 --- a/packages/image_picker/image_picker_android/example/pubspec.yaml +++ b/packages/image_picker/image_picker_android/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/image_picker/image_picker_android/pubspec.yaml b/packages/image_picker/image_picker_android/pubspec.yaml index 461ddfc1c437..7aa1a2258645 100755 --- a/packages/image_picker/image_picker_android/pubspec.yaml +++ b/packages/image_picker/image_picker_android/pubspec.yaml @@ -6,7 +6,7 @@ version: 0.8.5+5 environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/image_picker/image_picker_for_web/CHANGELOG.md b/packages/image_picker/image_picker_for_web/CHANGELOG.md index 8a5c089ef807..86c1bea873ae 100644 --- a/packages/image_picker/image_picker_for_web/CHANGELOG.md +++ b/packages/image_picker/image_picker_for_web/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 2.1.10 * Updates code for `no_leading_underscores_for_local_identifiers` lint. diff --git a/packages/image_picker/image_picker_for_web/example/pubspec.yaml b/packages/image_picker/image_picker_for_web/example/pubspec.yaml index c39bd81f9de0..96ce0dfa70c7 100644 --- a/packages/image_picker/image_picker_for_web/example/pubspec.yaml +++ b/packages/image_picker/image_picker_for_web/example/pubspec.yaml @@ -3,7 +3,7 @@ publish_to: none environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/image_picker/image_picker_for_web/pubspec.yaml b/packages/image_picker/image_picker_for_web/pubspec.yaml index c2e0975dda57..03c0fb3e3056 100644 --- a/packages/image_picker/image_picker_for_web/pubspec.yaml +++ b/packages/image_picker/image_picker_for_web/pubspec.yaml @@ -6,7 +6,7 @@ version: 2.1.10 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/image_picker/image_picker_ios/CHANGELOG.md b/packages/image_picker/image_picker_ios/CHANGELOG.md index d691b3201e69..04bb4dfdf890 100644 --- a/packages/image_picker/image_picker_ios/CHANGELOG.md +++ b/packages/image_picker/image_picker_ios/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 0.8.6+6 * Updates code for stricter lint checks. diff --git a/packages/image_picker/image_picker_ios/example/pubspec.yaml b/packages/image_picker/image_picker_ios/example/pubspec.yaml index 856f775cc641..bebe9bb04648 100755 --- a/packages/image_picker/image_picker_ios/example/pubspec.yaml +++ b/packages/image_picker/image_picker_ios/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/image_picker/image_picker_ios/pubspec.yaml b/packages/image_picker/image_picker_ios/pubspec.yaml index cfc22c89f18e..5bfb8852d2cc 100755 --- a/packages/image_picker/image_picker_ios/pubspec.yaml +++ b/packages/image_picker/image_picker_ios/pubspec.yaml @@ -6,7 +6,7 @@ version: 0.8.6+6 environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/image_picker/image_picker_platform_interface/CHANGELOG.md b/packages/image_picker/image_picker_platform_interface/CHANGELOG.md index 05b03a37cb98..91d6d80e6c23 100644 --- a/packages/image_picker/image_picker_platform_interface/CHANGELOG.md +++ b/packages/image_picker/image_picker_platform_interface/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 2.6.2 * Updates imports for `prefer_relative_imports`. diff --git a/packages/image_picker/image_picker_platform_interface/pubspec.yaml b/packages/image_picker/image_picker_platform_interface/pubspec.yaml index eb4d2b649eac..2f34ee2b349c 100644 --- a/packages/image_picker/image_picker_platform_interface/pubspec.yaml +++ b/packages/image_picker/image_picker_platform_interface/pubspec.yaml @@ -8,7 +8,7 @@ version: 2.6.2 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: cross_file: ^0.3.1+1 diff --git a/packages/image_picker/image_picker_windows/CHANGELOG.md b/packages/image_picker/image_picker_windows/CHANGELOG.md index 427598760a4b..5ea844d8e0a1 100644 --- a/packages/image_picker/image_picker_windows/CHANGELOG.md +++ b/packages/image_picker/image_picker_windows/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 0.1.0+3 * Changes XTypeGroup initialization from final to const. diff --git a/packages/image_picker/image_picker_windows/example/pubspec.yaml b/packages/image_picker/image_picker_windows/example/pubspec.yaml index b87000a6caff..bdbd182d3fc5 100644 --- a/packages/image_picker/image_picker_windows/example/pubspec.yaml +++ b/packages/image_picker/image_picker_windows/example/pubspec.yaml @@ -5,7 +5,7 @@ version: 1.0.0 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/image_picker/image_picker_windows/pubspec.yaml b/packages/image_picker/image_picker_windows/pubspec.yaml index 5d6988cc2931..e639bcbdbcd0 100644 --- a/packages/image_picker/image_picker_windows/pubspec.yaml +++ b/packages/image_picker/image_picker_windows/pubspec.yaml @@ -6,7 +6,7 @@ version: 0.1.0+3 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/in_app_purchase/in_app_purchase/CHANGELOG.md b/packages/in_app_purchase/in_app_purchase/CHANGELOG.md index c6d2270bbc46..1d76f145bee6 100644 --- a/packages/in_app_purchase/in_app_purchase/CHANGELOG.md +++ b/packages/in_app_purchase/in_app_purchase/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 3.1.1 * Adds screenshots to pubspec.yaml. diff --git a/packages/in_app_purchase/in_app_purchase/example/pubspec.yaml b/packages/in_app_purchase/in_app_purchase/example/pubspec.yaml index 6d2297572cb9..8037b1a4c1ef 100644 --- a/packages/in_app_purchase/in_app_purchase/example/pubspec.yaml +++ b/packages/in_app_purchase/in_app_purchase/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/in_app_purchase/in_app_purchase/pubspec.yaml b/packages/in_app_purchase/in_app_purchase/pubspec.yaml index 875e50688b06..71d415c72083 100644 --- a/packages/in_app_purchase/in_app_purchase/pubspec.yaml +++ b/packages/in_app_purchase/in_app_purchase/pubspec.yaml @@ -6,7 +6,7 @@ version: 3.1.1 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md b/packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md index 327fc5c0f053..48987c66b09b 100644 --- a/packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md +++ b/packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 0.2.3+9 * Updates `androidx.test.espresso:espresso-core` to 3.5.1. diff --git a/packages/in_app_purchase/in_app_purchase_android/example/pubspec.yaml b/packages/in_app_purchase/in_app_purchase_android/example/pubspec.yaml index af760a3ada46..d5a76b848093 100644 --- a/packages/in_app_purchase/in_app_purchase_android/example/pubspec.yaml +++ b/packages/in_app_purchase/in_app_purchase_android/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/in_app_purchase/in_app_purchase_android/pubspec.yaml b/packages/in_app_purchase/in_app_purchase_android/pubspec.yaml index 332cf850af04..7f36bf31f0bd 100644 --- a/packages/in_app_purchase/in_app_purchase_android/pubspec.yaml +++ b/packages/in_app_purchase/in_app_purchase_android/pubspec.yaml @@ -6,7 +6,7 @@ version: 0.2.3+9 environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/in_app_purchase/in_app_purchase_platform_interface/CHANGELOG.md b/packages/in_app_purchase/in_app_purchase_platform_interface/CHANGELOG.md index 17ba02986088..a408c2db2cd7 100644 --- a/packages/in_app_purchase/in_app_purchase_platform_interface/CHANGELOG.md +++ b/packages/in_app_purchase/in_app_purchase_platform_interface/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 1.3.2 * Updates imports for `prefer_relative_imports`. diff --git a/packages/in_app_purchase/in_app_purchase_platform_interface/pubspec.yaml b/packages/in_app_purchase/in_app_purchase_platform_interface/pubspec.yaml index 46e38b0a03fa..b3420161530b 100644 --- a/packages/in_app_purchase/in_app_purchase_platform_interface/pubspec.yaml +++ b/packages/in_app_purchase/in_app_purchase_platform_interface/pubspec.yaml @@ -8,7 +8,7 @@ version: 1.3.2 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md b/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md index 434caf425d00..6c89b75dcefb 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md +++ b/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 0.3.4+1 * Updates code for stricter lint checks. diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/pubspec.yaml b/packages/in_app_purchase/in_app_purchase_storekit/example/pubspec.yaml index e71b85d4b447..b06dd6a9a594 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/pubspec.yaml +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml b/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml index 339d12320a18..cb0243c4f56a 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml +++ b/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml @@ -6,7 +6,7 @@ version: 0.3.4+1 environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/ios_platform_images/CHANGELOG.md b/packages/ios_platform_images/CHANGELOG.md index 610c362a00db..1a2428d7470e 100644 --- a/packages/ios_platform_images/CHANGELOG.md +++ b/packages/ios_platform_images/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 0.2.1 * Updates minimum Flutter version to 3.3.0. diff --git a/packages/ios_platform_images/example/pubspec.yaml b/packages/ios_platform_images/example/pubspec.yaml index 6045b3f67cfc..49b09bd8b637 100644 --- a/packages/ios_platform_images/example/pubspec.yaml +++ b/packages/ios_platform_images/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: cupertino_icons: ^1.0.2 diff --git a/packages/local_auth/local_auth/CHANGELOG.md b/packages/local_auth/local_auth/CHANGELOG.md index 21fed07d895b..d5ad7aa9a28a 100644 --- a/packages/local_auth/local_auth/CHANGELOG.md +++ b/packages/local_auth/local_auth/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 2.1.3 * Updates minimum Flutter version to 2.10. diff --git a/packages/local_auth/local_auth/example/pubspec.yaml b/packages/local_auth/local_auth/example/pubspec.yaml index f7dc2fc5b9e7..e02065b6d16f 100644 --- a/packages/local_auth/local_auth/example/pubspec.yaml +++ b/packages/local_auth/local_auth/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/local_auth/local_auth/pubspec.yaml b/packages/local_auth/local_auth/pubspec.yaml index 722c993edb50..769de34b2bb6 100644 --- a/packages/local_auth/local_auth/pubspec.yaml +++ b/packages/local_auth/local_auth/pubspec.yaml @@ -7,7 +7,7 @@ version: 2.1.3 environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/local_auth/local_auth_android/CHANGELOG.md b/packages/local_auth/local_auth_android/CHANGELOG.md index f1ef6a5c797c..521f656bb8ec 100644 --- a/packages/local_auth/local_auth_android/CHANGELOG.md +++ b/packages/local_auth/local_auth_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 1.0.17 * Adds compatibility with `intl` 0.18.0. diff --git a/packages/local_auth/local_auth_android/example/pubspec.yaml b/packages/local_auth/local_auth_android/example/pubspec.yaml index c95b89ad0c2a..fddd6b50f815 100644 --- a/packages/local_auth/local_auth_android/example/pubspec.yaml +++ b/packages/local_auth/local_auth_android/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/local_auth/local_auth_android/pubspec.yaml b/packages/local_auth/local_auth_android/pubspec.yaml index 3ad8a4b20a82..aa05acb8a88c 100644 --- a/packages/local_auth/local_auth_android/pubspec.yaml +++ b/packages/local_auth/local_auth_android/pubspec.yaml @@ -6,7 +6,7 @@ version: 1.0.17 environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/local_auth/local_auth_ios/CHANGELOG.md b/packages/local_auth/local_auth_ios/CHANGELOG.md index fd4c918262bb..eca9612fa69e 100644 --- a/packages/local_auth/local_auth_ios/CHANGELOG.md +++ b/packages/local_auth/local_auth_ios/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 1.0.12 * Adds compatibility with `intl` 0.18.0. diff --git a/packages/local_auth/local_auth_ios/example/pubspec.yaml b/packages/local_auth/local_auth_ios/example/pubspec.yaml index 720d5a732bd5..21b17fae7288 100644 --- a/packages/local_auth/local_auth_ios/example/pubspec.yaml +++ b/packages/local_auth/local_auth_ios/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/local_auth/local_auth_ios/pubspec.yaml b/packages/local_auth/local_auth_ios/pubspec.yaml index c9daa48c1fae..ef2fa7fcdac7 100644 --- a/packages/local_auth/local_auth_ios/pubspec.yaml +++ b/packages/local_auth/local_auth_ios/pubspec.yaml @@ -6,7 +6,7 @@ version: 1.0.12 environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/local_auth/local_auth_platform_interface/CHANGELOG.md b/packages/local_auth/local_auth_platform_interface/CHANGELOG.md index 7b9518b57589..be2be0ced788 100644 --- a/packages/local_auth/local_auth_platform_interface/CHANGELOG.md +++ b/packages/local_auth/local_auth_platform_interface/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 1.0.6 * Removes unused `intl` dependency. diff --git a/packages/local_auth/local_auth_platform_interface/pubspec.yaml b/packages/local_auth/local_auth_platform_interface/pubspec.yaml index 70b9d0e5f0b6..bc54978fd3df 100644 --- a/packages/local_auth/local_auth_platform_interface/pubspec.yaml +++ b/packages/local_auth/local_auth_platform_interface/pubspec.yaml @@ -8,7 +8,7 @@ version: 1.0.6 environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/local_auth/local_auth_windows/CHANGELOG.md b/packages/local_auth/local_auth_windows/CHANGELOG.md index d7bc77d551e7..90aa8b6b31db 100644 --- a/packages/local_auth/local_auth_windows/CHANGELOG.md +++ b/packages/local_auth/local_auth_windows/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 1.0.5 * Switches internal implementation to Pigeon. diff --git a/packages/local_auth/local_auth_windows/example/pubspec.yaml b/packages/local_auth/local_auth_windows/example/pubspec.yaml index 4bb2671f6826..1a1387a0875d 100644 --- a/packages/local_auth/local_auth_windows/example/pubspec.yaml +++ b/packages/local_auth/local_auth_windows/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/local_auth/local_auth_windows/pubspec.yaml b/packages/local_auth/local_auth_windows/pubspec.yaml index 45671e1af9d0..9866eef50584 100644 --- a/packages/local_auth/local_auth_windows/pubspec.yaml +++ b/packages/local_auth/local_auth_windows/pubspec.yaml @@ -6,7 +6,7 @@ version: 1.0.5 environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/path_provider/path_provider/CHANGELOG.md b/packages/path_provider/path_provider/CHANGELOG.md index 70916ae48ead..0f5e8e6d7225 100644 --- a/packages/path_provider/path_provider/CHANGELOG.md +++ b/packages/path_provider/path_provider/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 2.0.12 * Switches to the new `path_provider_foundation` implementation package diff --git a/packages/path_provider/path_provider/example/pubspec.yaml b/packages/path_provider/path_provider/example/pubspec.yaml index 5964a267f96d..ffb878bcf146 100644 --- a/packages/path_provider/path_provider/example/pubspec.yaml +++ b/packages/path_provider/path_provider/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/path_provider/path_provider/pubspec.yaml b/packages/path_provider/path_provider/pubspec.yaml index ad39a3b16173..8c139ccbb87b 100644 --- a/packages/path_provider/path_provider/pubspec.yaml +++ b/packages/path_provider/path_provider/pubspec.yaml @@ -6,7 +6,7 @@ version: 2.0.12 environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/path_provider/path_provider_android/CHANGELOG.md b/packages/path_provider/path_provider_android/CHANGELOG.md index 4edf0f8d290f..acf99b7a5e25 100644 --- a/packages/path_provider/path_provider_android/CHANGELOG.md +++ b/packages/path_provider/path_provider_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 2.0.22 * Removes unused Guava dependency. diff --git a/packages/path_provider/path_provider_android/example/pubspec.yaml b/packages/path_provider/path_provider_android/example/pubspec.yaml index b460d6ba49ce..e53c44ffda68 100644 --- a/packages/path_provider/path_provider_android/example/pubspec.yaml +++ b/packages/path_provider/path_provider_android/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/path_provider/path_provider_android/pubspec.yaml b/packages/path_provider/path_provider_android/pubspec.yaml index 9a8df83f4922..dcdf938feee5 100644 --- a/packages/path_provider/path_provider_android/pubspec.yaml +++ b/packages/path_provider/path_provider_android/pubspec.yaml @@ -6,7 +6,7 @@ version: 2.0.22 environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/path_provider/path_provider_foundation/CHANGELOG.md b/packages/path_provider/path_provider_foundation/CHANGELOG.md index fbdd426ba46d..a65177eb2cde 100644 --- a/packages/path_provider/path_provider_foundation/CHANGELOG.md +++ b/packages/path_provider/path_provider_foundation/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 2.1.0 * Renames the package previously published as diff --git a/packages/path_provider/path_provider_foundation/example/pubspec.yaml b/packages/path_provider/path_provider_foundation/example/pubspec.yaml index 69524ad55e74..fcf599564659 100644 --- a/packages/path_provider/path_provider_foundation/example/pubspec.yaml +++ b/packages/path_provider/path_provider_foundation/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/path_provider/path_provider_foundation/pubspec.yaml b/packages/path_provider/path_provider_foundation/pubspec.yaml index 75d22e132793..a0d14c96eea0 100644 --- a/packages/path_provider/path_provider_foundation/pubspec.yaml +++ b/packages/path_provider/path_provider_foundation/pubspec.yaml @@ -6,7 +6,7 @@ version: 2.1.0 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/path_provider/path_provider_linux/CHANGELOG.md b/packages/path_provider/path_provider_linux/CHANGELOG.md index baf3283348de..d18e565f83f4 100644 --- a/packages/path_provider/path_provider_linux/CHANGELOG.md +++ b/packages/path_provider/path_provider_linux/CHANGELOG.md @@ -1,6 +1,6 @@ ## NEXT -* Updates minimum Flutter version to 2.10. +* Updates minimum Flutter version to 3.0. ## 2.1.7 diff --git a/packages/path_provider/path_provider_linux/example/pubspec.yaml b/packages/path_provider/path_provider_linux/example/pubspec.yaml index 8d8940ba2f05..a305575bb13b 100644 --- a/packages/path_provider/path_provider_linux/example/pubspec.yaml +++ b/packages/path_provider/path_provider_linux/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: "none" environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/path_provider/path_provider_linux/pubspec.yaml b/packages/path_provider/path_provider_linux/pubspec.yaml index 41d587360b5e..b0d86fed090e 100644 --- a/packages/path_provider/path_provider_linux/pubspec.yaml +++ b/packages/path_provider/path_provider_linux/pubspec.yaml @@ -6,7 +6,7 @@ version: 2.1.7 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/path_provider/path_provider_platform_interface/CHANGELOG.md b/packages/path_provider/path_provider_platform_interface/CHANGELOG.md index f12e1ec53ade..e3470dc36844 100644 --- a/packages/path_provider/path_provider_platform_interface/CHANGELOG.md +++ b/packages/path_provider/path_provider_platform_interface/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 2.0.5 * Updates imports for `prefer_relative_imports`. diff --git a/packages/path_provider/path_provider_platform_interface/pubspec.yaml b/packages/path_provider/path_provider_platform_interface/pubspec.yaml index 6ce7ec662b33..3ce20f6f85db 100644 --- a/packages/path_provider/path_provider_platform_interface/pubspec.yaml +++ b/packages/path_provider/path_provider_platform_interface/pubspec.yaml @@ -8,7 +8,7 @@ version: 2.0.5 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/path_provider/path_provider_windows/CHANGELOG.md b/packages/path_provider/path_provider_windows/CHANGELOG.md index 757f13dbb533..08920a9569e8 100644 --- a/packages/path_provider/path_provider_windows/CHANGELOG.md +++ b/packages/path_provider/path_provider_windows/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 2.1.3 * Updates minimum Flutter version to 2.10. diff --git a/packages/path_provider/path_provider_windows/example/pubspec.yaml b/packages/path_provider/path_provider_windows/example/pubspec.yaml index d70a4a84f504..306f20c354df 100644 --- a/packages/path_provider/path_provider_windows/example/pubspec.yaml +++ b/packages/path_provider/path_provider_windows/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/quick_actions/quick_actions/CHANGELOG.md b/packages/quick_actions/quick_actions/CHANGELOG.md index 7d1881596255..0787c27014f1 100644 --- a/packages/quick_actions/quick_actions/CHANGELOG.md +++ b/packages/quick_actions/quick_actions/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 1.0.1 * Updates implementaion package versions to current versions. diff --git a/packages/quick_actions/quick_actions/example/pubspec.yaml b/packages/quick_actions/quick_actions/example/pubspec.yaml index 1a10a653db06..c629384ee5e2 100644 --- a/packages/quick_actions/quick_actions/example/pubspec.yaml +++ b/packages/quick_actions/quick_actions/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/quick_actions/quick_actions/pubspec.yaml b/packages/quick_actions/quick_actions/pubspec.yaml index 08b486fe50e3..3f1bf57a70f0 100644 --- a/packages/quick_actions/quick_actions/pubspec.yaml +++ b/packages/quick_actions/quick_actions/pubspec.yaml @@ -7,7 +7,7 @@ version: 1.0.1 environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/quick_actions/quick_actions_android/CHANGELOG.md b/packages/quick_actions/quick_actions_android/CHANGELOG.md index bc809a4dc477..6587627b2145 100644 --- a/packages/quick_actions/quick_actions_android/CHANGELOG.md +++ b/packages/quick_actions/quick_actions_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 1.0.0 * Updates version to 1.0 to reflect current status. diff --git a/packages/quick_actions/quick_actions_android/example/pubspec.yaml b/packages/quick_actions/quick_actions_android/example/pubspec.yaml index c560d4dd5f1e..48a6fe9fd1a5 100644 --- a/packages/quick_actions/quick_actions_android/example/pubspec.yaml +++ b/packages/quick_actions/quick_actions_android/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.15.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/quick_actions/quick_actions_android/pubspec.yaml b/packages/quick_actions/quick_actions_android/pubspec.yaml index e47a1fdc13e9..038c8631287f 100644 --- a/packages/quick_actions/quick_actions_android/pubspec.yaml +++ b/packages/quick_actions/quick_actions_android/pubspec.yaml @@ -6,7 +6,7 @@ version: 1.0.0 environment: sdk: ">=2.15.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/quick_actions/quick_actions_ios/CHANGELOG.md b/packages/quick_actions/quick_actions_ios/CHANGELOG.md index bded35478899..e135fa4c9b69 100644 --- a/packages/quick_actions/quick_actions_ios/CHANGELOG.md +++ b/packages/quick_actions/quick_actions_ios/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 1.0.2 * Migrates remaining components to Swift and removes all Objective-C settings. diff --git a/packages/quick_actions/quick_actions_ios/example/pubspec.yaml b/packages/quick_actions/quick_actions_ios/example/pubspec.yaml index ecac371720d6..af0697022ea3 100644 --- a/packages/quick_actions/quick_actions_ios/example/pubspec.yaml +++ b/packages/quick_actions/quick_actions_ios/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.15.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/quick_actions/quick_actions_ios/pubspec.yaml b/packages/quick_actions/quick_actions_ios/pubspec.yaml index 6e7fb43dd7ed..2b7572368773 100644 --- a/packages/quick_actions/quick_actions_ios/pubspec.yaml +++ b/packages/quick_actions/quick_actions_ios/pubspec.yaml @@ -6,7 +6,7 @@ version: 1.0.2 environment: sdk: ">=2.15.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/quick_actions/quick_actions_platform_interface/CHANGELOG.md b/packages/quick_actions/quick_actions_platform_interface/CHANGELOG.md index 950864f96653..6bbfd5a35f67 100644 --- a/packages/quick_actions/quick_actions_platform_interface/CHANGELOG.md +++ b/packages/quick_actions/quick_actions_platform_interface/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 1.0.3 * Updates imports for `prefer_relative_imports`. diff --git a/packages/quick_actions/quick_actions_platform_interface/pubspec.yaml b/packages/quick_actions/quick_actions_platform_interface/pubspec.yaml index 2990da603c14..cfde0a76f5b2 100644 --- a/packages/quick_actions/quick_actions_platform_interface/pubspec.yaml +++ b/packages/quick_actions/quick_actions_platform_interface/pubspec.yaml @@ -8,7 +8,7 @@ version: 1.0.3 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/shared_preferences/shared_preferences/CHANGELOG.md b/packages/shared_preferences/shared_preferences/CHANGELOG.md index db0d6003b7fb..ed44436dfe1e 100644 --- a/packages/shared_preferences/shared_preferences/CHANGELOG.md +++ b/packages/shared_preferences/shared_preferences/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 2.0.17 * Updates code for stricter lint checks. diff --git a/packages/shared_preferences/shared_preferences/example/pubspec.yaml b/packages/shared_preferences/shared_preferences/example/pubspec.yaml index 6964656d16ef..944538da0d0c 100644 --- a/packages/shared_preferences/shared_preferences/example/pubspec.yaml +++ b/packages/shared_preferences/shared_preferences/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/shared_preferences/shared_preferences/pubspec.yaml b/packages/shared_preferences/shared_preferences/pubspec.yaml index aab200f252fb..30ee569c3ad3 100644 --- a/packages/shared_preferences/shared_preferences/pubspec.yaml +++ b/packages/shared_preferences/shared_preferences/pubspec.yaml @@ -7,7 +7,7 @@ version: 2.0.17 environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/shared_preferences/shared_preferences_android/CHANGELOG.md b/packages/shared_preferences/shared_preferences_android/CHANGELOG.md index 1e348a124702..727f2b626d81 100644 --- a/packages/shared_preferences/shared_preferences_android/CHANGELOG.md +++ b/packages/shared_preferences/shared_preferences_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 2.0.15 * Updates code for stricter lint checks. diff --git a/packages/shared_preferences/shared_preferences_android/example/pubspec.yaml b/packages/shared_preferences/shared_preferences_android/example/pubspec.yaml index bd1272c71d80..c0bc6668e3dd 100644 --- a/packages/shared_preferences/shared_preferences_android/example/pubspec.yaml +++ b/packages/shared_preferences/shared_preferences_android/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/shared_preferences/shared_preferences_android/pubspec.yaml b/packages/shared_preferences/shared_preferences_android/pubspec.yaml index 589c5b2f15fd..d968dcbce55b 100644 --- a/packages/shared_preferences/shared_preferences_android/pubspec.yaml +++ b/packages/shared_preferences/shared_preferences_android/pubspec.yaml @@ -6,7 +6,7 @@ version: 2.0.15 environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/shared_preferences/shared_preferences_foundation/CHANGELOG.md b/packages/shared_preferences/shared_preferences_foundation/CHANGELOG.md index 788b841c97f9..8b454e7a0236 100644 --- a/packages/shared_preferences/shared_preferences_foundation/CHANGELOG.md +++ b/packages/shared_preferences/shared_preferences_foundation/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 2.1.2 * Updates code for stricter lint checks. diff --git a/packages/shared_preferences/shared_preferences_foundation/example/pubspec.yaml b/packages/shared_preferences/shared_preferences_foundation/example/pubspec.yaml index 713f7b0c9ffe..ef67f234e7c5 100644 --- a/packages/shared_preferences/shared_preferences_foundation/example/pubspec.yaml +++ b/packages/shared_preferences/shared_preferences_foundation/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/shared_preferences/shared_preferences_foundation/pubspec.yaml b/packages/shared_preferences/shared_preferences_foundation/pubspec.yaml index 6641a3455fb5..caacacaeb52a 100644 --- a/packages/shared_preferences/shared_preferences_foundation/pubspec.yaml +++ b/packages/shared_preferences/shared_preferences_foundation/pubspec.yaml @@ -6,7 +6,7 @@ version: 2.1.2 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/shared_preferences/shared_preferences_linux/CHANGELOG.md b/packages/shared_preferences/shared_preferences_linux/CHANGELOG.md index 21eb16887d13..3c5a398546d1 100644 --- a/packages/shared_preferences/shared_preferences_linux/CHANGELOG.md +++ b/packages/shared_preferences/shared_preferences_linux/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 2.1.3 * Updates code for stricter lint checks. diff --git a/packages/shared_preferences/shared_preferences_linux/example/pubspec.yaml b/packages/shared_preferences/shared_preferences_linux/example/pubspec.yaml index 9418c0581ed7..98ff24a84682 100644 --- a/packages/shared_preferences/shared_preferences_linux/example/pubspec.yaml +++ b/packages/shared_preferences/shared_preferences_linux/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/shared_preferences/shared_preferences_linux/pubspec.yaml b/packages/shared_preferences/shared_preferences_linux/pubspec.yaml index a64fc61860b6..21203a877586 100644 --- a/packages/shared_preferences/shared_preferences_linux/pubspec.yaml +++ b/packages/shared_preferences/shared_preferences_linux/pubspec.yaml @@ -6,7 +6,7 @@ version: 2.1.3 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/shared_preferences/shared_preferences_platform_interface/CHANGELOG.md b/packages/shared_preferences/shared_preferences_platform_interface/CHANGELOG.md index 3ef89c396222..38cdf083ccda 100644 --- a/packages/shared_preferences/shared_preferences_platform_interface/CHANGELOG.md +++ b/packages/shared_preferences/shared_preferences_platform_interface/CHANGELOG.md @@ -1,6 +1,6 @@ ## NEXT -* Updates minimum Flutter version to 2.10. +* Updates minimum Flutter version to 3.0. ## 2.1.0 diff --git a/packages/shared_preferences/shared_preferences_platform_interface/pubspec.yaml b/packages/shared_preferences/shared_preferences_platform_interface/pubspec.yaml index b55eb1ccceb2..59d6409cff7a 100644 --- a/packages/shared_preferences/shared_preferences_platform_interface/pubspec.yaml +++ b/packages/shared_preferences/shared_preferences_platform_interface/pubspec.yaml @@ -6,7 +6,7 @@ version: 2.1.0 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/shared_preferences/shared_preferences_web/CHANGELOG.md b/packages/shared_preferences/shared_preferences_web/CHANGELOG.md index 8c8411da6fff..6332663b4b47 100644 --- a/packages/shared_preferences/shared_preferences_web/CHANGELOG.md +++ b/packages/shared_preferences/shared_preferences_web/CHANGELOG.md @@ -1,6 +1,6 @@ ## NEXT -* Updates minimum Flutter version to 2.10. +* Updates minimum Flutter version to 3.0. ## 2.0.4 diff --git a/packages/shared_preferences/shared_preferences_web/example/pubspec.yaml b/packages/shared_preferences/shared_preferences_web/example/pubspec.yaml index 050275489efa..52cfa1b436fb 100644 --- a/packages/shared_preferences/shared_preferences_web/example/pubspec.yaml +++ b/packages/shared_preferences/shared_preferences_web/example/pubspec.yaml @@ -3,7 +3,7 @@ publish_to: none environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/shared_preferences/shared_preferences_web/pubspec.yaml b/packages/shared_preferences/shared_preferences_web/pubspec.yaml index b64f37d10da6..942fe12a39a1 100644 --- a/packages/shared_preferences/shared_preferences_web/pubspec.yaml +++ b/packages/shared_preferences/shared_preferences_web/pubspec.yaml @@ -6,7 +6,7 @@ version: 2.0.4 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/shared_preferences/shared_preferences_windows/CHANGELOG.md b/packages/shared_preferences/shared_preferences_windows/CHANGELOG.md index dcf99985843e..b99e3dd6f6ec 100644 --- a/packages/shared_preferences/shared_preferences_windows/CHANGELOG.md +++ b/packages/shared_preferences/shared_preferences_windows/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 2.1.3 * Updates code for stricter lint checks. diff --git a/packages/shared_preferences/shared_preferences_windows/example/pubspec.yaml b/packages/shared_preferences/shared_preferences_windows/example/pubspec.yaml index 43c2145b32ae..bb51f7fbef18 100644 --- a/packages/shared_preferences/shared_preferences_windows/example/pubspec.yaml +++ b/packages/shared_preferences/shared_preferences_windows/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/shared_preferences/shared_preferences_windows/pubspec.yaml b/packages/shared_preferences/shared_preferences_windows/pubspec.yaml index 600a520b7bd2..03fc31c6301e 100644 --- a/packages/shared_preferences/shared_preferences_windows/pubspec.yaml +++ b/packages/shared_preferences/shared_preferences_windows/pubspec.yaml @@ -6,7 +6,7 @@ version: 2.1.3 environment: sdk: '>=2.12.0 <3.0.0' - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/url_launcher/url_launcher/CHANGELOG.md b/packages/url_launcher/url_launcher/CHANGELOG.md index 6244bb418a36..dbb33aa34bba 100644 --- a/packages/url_launcher/url_launcher/CHANGELOG.md +++ b/packages/url_launcher/url_launcher/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 6.1.8 * Updates code for stricter lint checks. diff --git a/packages/url_launcher/url_launcher/example/pubspec.yaml b/packages/url_launcher/url_launcher/example/pubspec.yaml index 573dc0d9ed01..83900bfdef75 100644 --- a/packages/url_launcher/url_launcher/example/pubspec.yaml +++ b/packages/url_launcher/url_launcher/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/url_launcher/url_launcher/pubspec.yaml b/packages/url_launcher/url_launcher/pubspec.yaml index 4f2fae2d6b62..e2b3ed534dbf 100644 --- a/packages/url_launcher/url_launcher/pubspec.yaml +++ b/packages/url_launcher/url_launcher/pubspec.yaml @@ -7,7 +7,7 @@ version: 6.1.8 environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/url_launcher/url_launcher_android/CHANGELOG.md b/packages/url_launcher/url_launcher_android/CHANGELOG.md index 1dbc4af784fb..1062de50c4ca 100644 --- a/packages/url_launcher/url_launcher_android/CHANGELOG.md +++ b/packages/url_launcher/url_launcher_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 6.0.23 * Updates code for stricter lint checks. diff --git a/packages/url_launcher/url_launcher_android/example/pubspec.yaml b/packages/url_launcher/url_launcher_android/example/pubspec.yaml index 6c922c7a0f7d..33fc9f06ed63 100644 --- a/packages/url_launcher/url_launcher_android/example/pubspec.yaml +++ b/packages/url_launcher/url_launcher_android/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/url_launcher/url_launcher_android/pubspec.yaml b/packages/url_launcher/url_launcher_android/pubspec.yaml index 3d8a3ad70144..599274a95ebc 100644 --- a/packages/url_launcher/url_launcher_android/pubspec.yaml +++ b/packages/url_launcher/url_launcher_android/pubspec.yaml @@ -6,7 +6,7 @@ version: 6.0.23 environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/url_launcher/url_launcher_ios/CHANGELOG.md b/packages/url_launcher/url_launcher_ios/CHANGELOG.md index 7919b16f4623..058520764c88 100644 --- a/packages/url_launcher/url_launcher_ios/CHANGELOG.md +++ b/packages/url_launcher/url_launcher_ios/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 6.0.18 * Updates code for stricter lint checks. diff --git a/packages/url_launcher/url_launcher_ios/example/pubspec.yaml b/packages/url_launcher/url_launcher_ios/example/pubspec.yaml index 9a134c747fa4..d170d3617442 100644 --- a/packages/url_launcher/url_launcher_ios/example/pubspec.yaml +++ b/packages/url_launcher/url_launcher_ios/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/url_launcher/url_launcher_ios/pubspec.yaml b/packages/url_launcher/url_launcher_ios/pubspec.yaml index 5af4cf3f3666..5817a9a44051 100644 --- a/packages/url_launcher/url_launcher_ios/pubspec.yaml +++ b/packages/url_launcher/url_launcher_ios/pubspec.yaml @@ -6,7 +6,7 @@ version: 6.0.18 environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/url_launcher/url_launcher_linux/CHANGELOG.md b/packages/url_launcher/url_launcher_linux/CHANGELOG.md index 24aec1bd9b34..3d955871c8c8 100644 --- a/packages/url_launcher/url_launcher_linux/CHANGELOG.md +++ b/packages/url_launcher/url_launcher_linux/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 3.0.2 * Updates code for stricter lint checks. diff --git a/packages/url_launcher/url_launcher_linux/example/pubspec.yaml b/packages/url_launcher/url_launcher_linux/example/pubspec.yaml index 17effeb1ffcb..ba738806af38 100644 --- a/packages/url_launcher/url_launcher_linux/example/pubspec.yaml +++ b/packages/url_launcher/url_launcher_linux/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/url_launcher/url_launcher_linux/pubspec.yaml b/packages/url_launcher/url_launcher_linux/pubspec.yaml index 20b7886628c3..e455ab83bef5 100644 --- a/packages/url_launcher/url_launcher_linux/pubspec.yaml +++ b/packages/url_launcher/url_launcher_linux/pubspec.yaml @@ -6,7 +6,7 @@ version: 3.0.2 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/url_launcher/url_launcher_macos/CHANGELOG.md b/packages/url_launcher/url_launcher_macos/CHANGELOG.md index d85da2c2c6e3..eb42ba920e23 100644 --- a/packages/url_launcher/url_launcher_macos/CHANGELOG.md +++ b/packages/url_launcher/url_launcher_macos/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 3.0.2 * Updates code for stricter lint checks. diff --git a/packages/url_launcher/url_launcher_macos/example/pubspec.yaml b/packages/url_launcher/url_launcher_macos/example/pubspec.yaml index 3b802ea229ba..688cac3a6b0e 100644 --- a/packages/url_launcher/url_launcher_macos/example/pubspec.yaml +++ b/packages/url_launcher/url_launcher_macos/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/url_launcher/url_launcher_macos/pubspec.yaml b/packages/url_launcher/url_launcher_macos/pubspec.yaml index 82aef73f6ed1..2ec915fc2ddb 100644 --- a/packages/url_launcher/url_launcher_macos/pubspec.yaml +++ b/packages/url_launcher/url_launcher_macos/pubspec.yaml @@ -6,7 +6,7 @@ version: 3.0.2 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/url_launcher/url_launcher_platform_interface/CHANGELOG.md b/packages/url_launcher/url_launcher_platform_interface/CHANGELOG.md index d45ca36e3906..fecd2a45c4cb 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 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 2.1.1 * Updates imports for `prefer_relative_imports`. diff --git a/packages/url_launcher/url_launcher_platform_interface/pubspec.yaml b/packages/url_launcher/url_launcher_platform_interface/pubspec.yaml index 4364e116c508..ab37dc32eedd 100644 --- a/packages/url_launcher/url_launcher_platform_interface/pubspec.yaml +++ b/packages/url_launcher/url_launcher_platform_interface/pubspec.yaml @@ -8,7 +8,7 @@ version: 2.1.1 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/url_launcher/url_launcher_web/CHANGELOG.md b/packages/url_launcher/url_launcher_web/CHANGELOG.md index da22933efe06..51b2de90b88a 100644 --- a/packages/url_launcher/url_launcher_web/CHANGELOG.md +++ b/packages/url_launcher/url_launcher_web/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 2.0.14 * Updates code for stricter lint checks. diff --git a/packages/url_launcher/url_launcher_web/example/pubspec.yaml b/packages/url_launcher/url_launcher_web/example/pubspec.yaml index f972b2857ecf..ca1b0d6634a7 100644 --- a/packages/url_launcher/url_launcher_web/example/pubspec.yaml +++ b/packages/url_launcher/url_launcher_web/example/pubspec.yaml @@ -3,7 +3,7 @@ publish_to: none environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/url_launcher/url_launcher_web/pubspec.yaml b/packages/url_launcher/url_launcher_web/pubspec.yaml index cd5cc49ea08e..8c8214ef6e4b 100644 --- a/packages/url_launcher/url_launcher_web/pubspec.yaml +++ b/packages/url_launcher/url_launcher_web/pubspec.yaml @@ -6,7 +6,7 @@ version: 2.0.14 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/url_launcher/url_launcher_windows/CHANGELOG.md b/packages/url_launcher/url_launcher_windows/CHANGELOG.md index 8fac4e7f72e3..07a5ef3ee8c0 100644 --- a/packages/url_launcher/url_launcher_windows/CHANGELOG.md +++ b/packages/url_launcher/url_launcher_windows/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 3.0.2 * Updates code for stricter lint checks. diff --git a/packages/url_launcher/url_launcher_windows/example/pubspec.yaml b/packages/url_launcher/url_launcher_windows/example/pubspec.yaml index 966d32c779e8..231d3d0848bc 100644 --- a/packages/url_launcher/url_launcher_windows/example/pubspec.yaml +++ b/packages/url_launcher/url_launcher_windows/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/url_launcher/url_launcher_windows/pubspec.yaml b/packages/url_launcher/url_launcher_windows/pubspec.yaml index 6da67ed28d60..63ca778f2af9 100644 --- a/packages/url_launcher/url_launcher_windows/pubspec.yaml +++ b/packages/url_launcher/url_launcher_windows/pubspec.yaml @@ -6,7 +6,7 @@ version: 3.0.2 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/video_player/video_player/CHANGELOG.md b/packages/video_player/video_player/CHANGELOG.md index 2df6f5dc5542..eed3b6bc2346 100644 --- a/packages/video_player/video_player/CHANGELOG.md +++ b/packages/video_player/video_player/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 2.5.1 * Updates code for stricter lint checks. diff --git a/packages/video_player/video_player/example/pubspec.yaml b/packages/video_player/video_player/example/pubspec.yaml index 7b6aa09329fa..0b30e9fb01e7 100644 --- a/packages/video_player/video_player/example/pubspec.yaml +++ b/packages/video_player/video_player/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/video_player/video_player/pubspec.yaml b/packages/video_player/video_player/pubspec.yaml index 5757ba9c8016..d75456ace469 100644 --- a/packages/video_player/video_player/pubspec.yaml +++ b/packages/video_player/video_player/pubspec.yaml @@ -7,7 +7,7 @@ version: 2.5.1 environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/video_player/video_player_android/CHANGELOG.md b/packages/video_player/video_player_android/CHANGELOG.md index bd71d5fa11e6..56024c4ba233 100644 --- a/packages/video_player/video_player_android/CHANGELOG.md +++ b/packages/video_player/video_player_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 2.3.10 * Adds compatibilty with version 6.0 of the platform interface. diff --git a/packages/video_player/video_player_android/example/pubspec.yaml b/packages/video_player/video_player_android/example/pubspec.yaml index 29865ed3cc24..16ffe17e7ba3 100644 --- a/packages/video_player/video_player_android/example/pubspec.yaml +++ b/packages/video_player/video_player_android/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/video_player/video_player_android/pubspec.yaml b/packages/video_player/video_player_android/pubspec.yaml index 2e1321f3bb43..3f46ec8a4d79 100644 --- a/packages/video_player/video_player_android/pubspec.yaml +++ b/packages/video_player/video_player_android/pubspec.yaml @@ -6,7 +6,7 @@ version: 2.3.10 environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/video_player/video_player_avfoundation/CHANGELOG.md b/packages/video_player/video_player_avfoundation/CHANGELOG.md index cf9c035abcda..b8564c0a2236 100644 --- a/packages/video_player/video_player_avfoundation/CHANGELOG.md +++ b/packages/video_player/video_player_avfoundation/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 2.3.8 * Adds compatibilty with version 6.0 of the platform interface. diff --git a/packages/video_player/video_player_avfoundation/example/pubspec.yaml b/packages/video_player/video_player_avfoundation/example/pubspec.yaml index f101b697a9cb..422fb91e35e5 100644 --- a/packages/video_player/video_player_avfoundation/example/pubspec.yaml +++ b/packages/video_player/video_player_avfoundation/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/video_player/video_player_avfoundation/pubspec.yaml b/packages/video_player/video_player_avfoundation/pubspec.yaml index 116edde94955..a5204137af20 100644 --- a/packages/video_player/video_player_avfoundation/pubspec.yaml +++ b/packages/video_player/video_player_avfoundation/pubspec.yaml @@ -6,7 +6,7 @@ version: 2.3.8 environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/video_player/video_player_platform_interface/CHANGELOG.md b/packages/video_player/video_player_platform_interface/CHANGELOG.md index fb7a3b340ca9..e1acbf578027 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 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 6.0.1 * Fixes comment describing file URI construction. diff --git a/packages/video_player/video_player_platform_interface/pubspec.yaml b/packages/video_player/video_player_platform_interface/pubspec.yaml index 36c0a7181845..8c6a8f400bb2 100644 --- a/packages/video_player/video_player_platform_interface/pubspec.yaml +++ b/packages/video_player/video_player_platform_interface/pubspec.yaml @@ -8,7 +8,7 @@ version: 6.0.1 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/video_player/video_player_web/CHANGELOG.md b/packages/video_player/video_player_web/CHANGELOG.md index 1f6dc951a6ff..42355439ce12 100644 --- a/packages/video_player/video_player_web/CHANGELOG.md +++ b/packages/video_player/video_player_web/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 2.0.13 * Adds compatibilty with version 6.0 of the platform interface. diff --git a/packages/video_player/video_player_web/example/pubspec.yaml b/packages/video_player/video_player_web/example/pubspec.yaml index 1d12b4ffcfd4..c4de1ce54c1a 100644 --- a/packages/video_player/video_player_web/example/pubspec.yaml +++ b/packages/video_player/video_player_web/example/pubspec.yaml @@ -3,7 +3,7 @@ publish_to: none environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/video_player/video_player_web/pubspec.yaml b/packages/video_player/video_player_web/pubspec.yaml index 0cd1c11f7f38..5e603034dd28 100644 --- a/packages/video_player/video_player_web/pubspec.yaml +++ b/packages/video_player/video_player_web/pubspec.yaml @@ -6,7 +6,7 @@ version: 2.0.13 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/webview_flutter/webview_flutter_platform_interface/CHANGELOG.md b/packages/webview_flutter/webview_flutter_platform_interface/CHANGELOG.md index 19a7950e45ab..5c33fdbcea59 100644 --- a/packages/webview_flutter/webview_flutter_platform_interface/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter_platform_interface/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 2.0.1 * Improves error message when a platform interface class is used before `WebViewPlatform.instance` has been set. diff --git a/packages/webview_flutter/webview_flutter_platform_interface/pubspec.yaml b/packages/webview_flutter/webview_flutter_platform_interface/pubspec.yaml index 865307245079..627b6098c302 100644 --- a/packages/webview_flutter/webview_flutter_platform_interface/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_platform_interface/pubspec.yaml @@ -8,7 +8,7 @@ version: 2.0.1 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/webview_flutter/webview_flutter_web/CHANGELOG.md b/packages/webview_flutter/webview_flutter_web/CHANGELOG.md index 19d7a1c48181..028e03d715ff 100644 --- a/packages/webview_flutter/webview_flutter_web/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter_web/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum Flutter version to 3.0. + ## 0.2.1 * Adds auto registration of the `WebViewPlatform` implementation. diff --git a/packages/webview_flutter/webview_flutter_web/pubspec.yaml b/packages/webview_flutter/webview_flutter_web/pubspec.yaml index 99ff3d95f269..66b67f4d67bd 100644 --- a/packages/webview_flutter/webview_flutter_web/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_web/pubspec.yaml @@ -6,7 +6,7 @@ version: 0.2.1 environment: sdk: ">=2.14.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: From 8bab180a668a88cb6638721728995e5048cbc7a2 Mon Sep 17 00:00:00 2001 From: Todd Volkert Date: Tue, 24 Jan 2023 19:16:00 -0800 Subject: [PATCH 033/130] Ignore deprecated member use on DecoderBufferCallback (#7014) * Ignore deprecated member use on DecoderBufferCallback In preparation for flutter/flutter#118966 * Bump version --- packages/ios_platform_images/CHANGELOG.md | 4 ++-- packages/ios_platform_images/lib/ios_platform_images.dart | 6 ++++-- packages/ios_platform_images/pubspec.yaml | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/ios_platform_images/CHANGELOG.md b/packages/ios_platform_images/CHANGELOG.md index 1a2428d7470e..63f10450cfd7 100644 --- a/packages/ios_platform_images/CHANGELOG.md +++ b/packages/ios_platform_images/CHANGELOG.md @@ -1,6 +1,6 @@ -## NEXT +## 0.2.1+1 -* Updates minimum Flutter version to 3.0. +* Add lint ignore comments ## 0.2.1 diff --git a/packages/ios_platform_images/lib/ios_platform_images.dart b/packages/ios_platform_images/lib/ios_platform_images.dart index aeb875ad2463..b372d362f6f7 100644 --- a/packages/ios_platform_images/lib/ios_platform_images.dart +++ b/packages/ios_platform_images/lib/ios_platform_images.dart @@ -63,7 +63,9 @@ class _FutureMemoryImage extends ImageProvider<_FutureMemoryImage> { @override ImageStreamCompleter loadBuffer( - _FutureMemoryImage key, DecoderBufferCallback decode) { + _FutureMemoryImage key, + DecoderBufferCallback decode, // ignore: deprecated_member_use + ) { return _FutureImageStreamCompleter( codec: _loadAsync(key, decode), futureScale: _futureScale, @@ -72,7 +74,7 @@ class _FutureMemoryImage extends ImageProvider<_FutureMemoryImage> { Future _loadAsync( _FutureMemoryImage key, - DecoderBufferCallback decode, + DecoderBufferCallback decode, // ignore: deprecated_member_use ) { assert(key == this); return _futureBytes.then(ui.ImmutableBuffer.fromUint8List).then(decode); diff --git a/packages/ios_platform_images/pubspec.yaml b/packages/ios_platform_images/pubspec.yaml index 17fb8850ac1d..6a321fed4875 100644 --- a/packages/ios_platform_images/pubspec.yaml +++ b/packages/ios_platform_images/pubspec.yaml @@ -2,7 +2,7 @@ name: ios_platform_images description: A plugin to share images between Flutter and iOS in add-to-app setups. repository: https://github.com/flutter/plugins/tree/main/packages/ios_platform_images issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+ios_platform_images%22 -version: 0.2.1 +version: 0.2.1+1 environment: sdk: ">=2.14.0 <3.0.0" From 1e5efd144f93f3f5b5f804272c126af3185fd2f6 Mon Sep 17 00:00:00 2001 From: stuartmorgan Date: Wed, 25 Jan 2023 08:55:08 -0800 Subject: [PATCH 034/130] [various] Enable use_build_context_synchronously (#6585) Enables the `use_build_context_synchronously` lint, and fixes violations. Part of https://github.com/flutter/flutter/issues/76229 --- analysis_options.yaml | 2 +- .../file_selector/file_selector/CHANGELOG.md | 1 + .../example/lib/get_directory_page.dart | 10 +-- .../example/lib/open_image_page.dart | 10 +-- .../lib/open_multiple_images_page.dart | 10 +-- .../example/lib/open_text_page.dart | 10 +-- .../file_selector_ios/CHANGELOG.md | 1 + .../example/lib/open_image_page.dart | 10 +-- .../lib/open_multiple_images_page.dart | 10 +-- .../example/lib/open_text_page.dart | 10 +-- .../file_selector_linux/CHANGELOG.md | 1 + .../example/lib/get_directory_page.dart | 10 +-- .../lib/get_multiple_directories_page.dart | 11 +-- .../example/lib/open_image_page.dart | 10 +-- .../lib/open_multiple_images_page.dart | 10 +-- .../example/lib/open_text_page.dart | 10 +-- .../file_selector_macos/CHANGELOG.md | 1 + .../example/lib/get_directory_page.dart | 10 +-- .../example/lib/open_image_page.dart | 10 +-- .../lib/open_multiple_images_page.dart | 10 +-- .../example/lib/open_text_page.dart | 10 +-- .../file_selector_windows/CHANGELOG.md | 1 + .../example/lib/get_directory_page.dart | 10 +-- .../example/lib/open_image_page.dart | 10 +-- .../lib/open_multiple_images_page.dart | 10 +-- .../example/lib/open_text_page.dart | 10 +-- .../image_picker_windows/CHANGELOG.md | 3 +- .../example/lib/main.dart | 32 +++++---- .../image_picker_windows/pubspec.yaml | 2 +- .../in_app_purchase/CHANGELOG.md | 3 +- .../in_app_purchase/example/lib/main.dart | 24 ++++--- .../in_app_purchase/pubspec.yaml | 2 +- .../webview_flutter/CHANGELOG.md | 4 ++ .../webview_flutter/example/lib/main.dart | 72 +++++++++++-------- .../webview_flutter/pubspec.yaml | 2 +- .../webview_flutter_android/CHANGELOG.md | 4 ++ .../example/lib/main.dart | 72 +++++++++++-------- .../webview_flutter_android/pubspec.yaml | 2 +- .../webview_flutter_wkwebview/CHANGELOG.md | 4 ++ .../example/lib/main.dart | 72 +++++++++++-------- .../webview_flutter_wkwebview/pubspec.yaml | 2 +- 41 files changed, 304 insertions(+), 204 deletions(-) diff --git a/analysis_options.yaml b/analysis_options.yaml index 27f59e1bd5bd..1eb0d232ce1b 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -241,7 +241,7 @@ linter: - unnecessary_to_list_in_spreads - unrelated_type_equality_checks - unsafe_html - # - use_build_context_synchronously # LOCAL CHANGE - Needs to be enabled and violations fixed. + - use_build_context_synchronously # - use_colored_box # not yet tested # - use_decorated_box # not yet tested # - use_enums # not yet tested diff --git a/packages/file_selector/file_selector/CHANGELOG.md b/packages/file_selector/file_selector/CHANGELOG.md index b8784e518c5a..9fd2341501b3 100644 --- a/packages/file_selector/file_selector/CHANGELOG.md +++ b/packages/file_selector/file_selector/CHANGELOG.md @@ -1,5 +1,6 @@ ## NEXT +* Updates example code for `use_build_context_synchronously` lint. * Updates minimum Flutter version to 3.0. ## 0.9.2+2 diff --git a/packages/file_selector/file_selector/example/lib/get_directory_page.dart b/packages/file_selector/file_selector/example/lib/get_directory_page.dart index de80aa56be56..dfe166db96c4 100644 --- a/packages/file_selector/file_selector/example/lib/get_directory_page.dart +++ b/packages/file_selector/file_selector/example/lib/get_directory_page.dart @@ -22,10 +22,12 @@ class GetDirectoryPage extends StatelessWidget { // Operation was canceled by the user. return; } - await showDialog( - context: context, - builder: (BuildContext context) => TextDisplay(directoryPath), - ); + if (context.mounted) { + await showDialog( + context: context, + builder: (BuildContext context) => TextDisplay(directoryPath), + ); + } } @override diff --git a/packages/file_selector/file_selector/example/lib/open_image_page.dart b/packages/file_selector/file_selector/example/lib/open_image_page.dart index ba18e6e78594..7717f28c39fe 100644 --- a/packages/file_selector/file_selector/example/lib/open_image_page.dart +++ b/packages/file_selector/file_selector/example/lib/open_image_page.dart @@ -29,10 +29,12 @@ class OpenImagePage extends StatelessWidget { final String fileName = file.name; final String filePath = file.path; - await showDialog( - context: context, - builder: (BuildContext context) => ImageDisplay(fileName, filePath), - ); + if (context.mounted) { + await showDialog( + context: context, + builder: (BuildContext context) => ImageDisplay(fileName, filePath), + ); + } } @override diff --git a/packages/file_selector/file_selector/example/lib/open_multiple_images_page.dart b/packages/file_selector/file_selector/example/lib/open_multiple_images_page.dart index 8ae83c2a85dc..a09a6db9d7a7 100644 --- a/packages/file_selector/file_selector/example/lib/open_multiple_images_page.dart +++ b/packages/file_selector/file_selector/example/lib/open_multiple_images_page.dart @@ -32,10 +32,12 @@ class OpenMultipleImagesPage extends StatelessWidget { // Operation was canceled by the user. return; } - await showDialog( - context: context, - builder: (BuildContext context) => MultipleImagesDisplay(files), - ); + if (context.mounted) { + await showDialog( + context: context, + builder: (BuildContext context) => MultipleImagesDisplay(files), + ); + } } @override diff --git a/packages/file_selector/file_selector/example/lib/open_text_page.dart b/packages/file_selector/file_selector/example/lib/open_text_page.dart index f052db1eefc1..e28a67a02ddf 100644 --- a/packages/file_selector/file_selector/example/lib/open_text_page.dart +++ b/packages/file_selector/file_selector/example/lib/open_text_page.dart @@ -32,10 +32,12 @@ class OpenTextPage extends StatelessWidget { final String fileName = file.name; final String fileContent = await file.readAsString(); - await showDialog( - context: context, - builder: (BuildContext context) => TextDisplay(fileName, fileContent), - ); + if (context.mounted) { + await showDialog( + context: context, + builder: (BuildContext context) => TextDisplay(fileName, fileContent), + ); + } } @override diff --git a/packages/file_selector/file_selector_ios/CHANGELOG.md b/packages/file_selector/file_selector_ios/CHANGELOG.md index ccb51eff9ffd..40d232ed25d0 100644 --- a/packages/file_selector/file_selector_ios/CHANGELOG.md +++ b/packages/file_selector/file_selector_ios/CHANGELOG.md @@ -1,5 +1,6 @@ ## NEXT +* Updates example code for `use_build_context_synchronously` lint. * Updates minimum Flutter version to 3.0. ## 0.5.0+2 diff --git a/packages/file_selector/file_selector_ios/example/lib/open_image_page.dart b/packages/file_selector/file_selector_ios/example/lib/open_image_page.dart index 606a64870566..6fcbcbfbafd6 100644 --- a/packages/file_selector/file_selector_ios/example/lib/open_image_page.dart +++ b/packages/file_selector/file_selector_ios/example/lib/open_image_page.dart @@ -29,10 +29,12 @@ class OpenImagePage extends StatelessWidget { final String fileName = file.name; final String filePath = file.path; - await showDialog( - context: context, - builder: (BuildContext context) => ImageDisplay(fileName, filePath), - ); + if (context.mounted) { + await showDialog( + context: context, + builder: (BuildContext context) => ImageDisplay(fileName, filePath), + ); + } } @override diff --git a/packages/file_selector/file_selector_ios/example/lib/open_multiple_images_page.dart b/packages/file_selector/file_selector_ios/example/lib/open_multiple_images_page.dart index adc4a65f12b5..30cc5159b060 100644 --- a/packages/file_selector/file_selector_ios/example/lib/open_multiple_images_page.dart +++ b/packages/file_selector/file_selector_ios/example/lib/open_multiple_images_page.dart @@ -34,10 +34,12 @@ class OpenMultipleImagesPage extends StatelessWidget { // Operation was canceled by the user. return; } - await showDialog( - context: context, - builder: (BuildContext context) => MultipleImagesDisplay(files), - ); + if (context.mounted) { + await showDialog( + context: context, + builder: (BuildContext context) => MultipleImagesDisplay(files), + ); + } } @override diff --git a/packages/file_selector/file_selector_ios/example/lib/open_text_page.dart b/packages/file_selector/file_selector_ios/example/lib/open_text_page.dart index e7bbf8bc937f..f21daf9a96bf 100644 --- a/packages/file_selector/file_selector_ios/example/lib/open_text_page.dart +++ b/packages/file_selector/file_selector_ios/example/lib/open_text_page.dart @@ -26,10 +26,12 @@ class OpenTextPage extends StatelessWidget { final String fileName = file.name; final String fileContent = await file.readAsString(); - await showDialog( - context: context, - builder: (BuildContext context) => TextDisplay(fileName, fileContent), - ); + if (context.mounted) { + await showDialog( + context: context, + builder: (BuildContext context) => TextDisplay(fileName, fileContent), + ); + } } @override diff --git a/packages/file_selector/file_selector_linux/CHANGELOG.md b/packages/file_selector/file_selector_linux/CHANGELOG.md index 00ccb06d7b38..6f7853cc5f13 100644 --- a/packages/file_selector/file_selector_linux/CHANGELOG.md +++ b/packages/file_selector/file_selector_linux/CHANGELOG.md @@ -1,5 +1,6 @@ ## NEXT +* Updates example code for `use_build_context_synchronously` lint. * Updates minimum Flutter version to 3.0. ## 0.9.1 diff --git a/packages/file_selector/file_selector_linux/example/lib/get_directory_page.dart b/packages/file_selector/file_selector_linux/example/lib/get_directory_page.dart index 0699dd121541..f6390ccef20d 100644 --- a/packages/file_selector/file_selector_linux/example/lib/get_directory_page.dart +++ b/packages/file_selector/file_selector_linux/example/lib/get_directory_page.dart @@ -21,10 +21,12 @@ class GetDirectoryPage extends StatelessWidget { // Operation was canceled by the user. return; } - await showDialog( - context: context, - builder: (BuildContext context) => TextDisplay(directoryPath), - ); + if (context.mounted) { + await showDialog( + context: context, + builder: (BuildContext context) => TextDisplay(directoryPath), + ); + } } @override diff --git a/packages/file_selector/file_selector_linux/example/lib/get_multiple_directories_page.dart b/packages/file_selector/file_selector_linux/example/lib/get_multiple_directories_page.dart index 66ab29cfdd9b..087240be765e 100644 --- a/packages/file_selector/file_selector_linux/example/lib/get_multiple_directories_page.dart +++ b/packages/file_selector/file_selector_linux/example/lib/get_multiple_directories_page.dart @@ -21,10 +21,13 @@ class GetMultipleDirectoriesPage extends StatelessWidget { // Operation was canceled by the user. return; } - await showDialog( - context: context, - builder: (BuildContext context) => TextDisplay(directoryPaths.join('\n')), - ); + if (context.mounted) { + await showDialog( + context: context, + builder: (BuildContext context) => + TextDisplay(directoryPaths.join('\n')), + ); + } } @override diff --git a/packages/file_selector/file_selector_linux/example/lib/open_image_page.dart b/packages/file_selector/file_selector_linux/example/lib/open_image_page.dart index b6ada56ebb2b..9252d25f113c 100644 --- a/packages/file_selector/file_selector_linux/example/lib/open_image_page.dart +++ b/packages/file_selector/file_selector_linux/example/lib/open_image_page.dart @@ -28,10 +28,12 @@ class OpenImagePage extends StatelessWidget { final String fileName = file.name; final String filePath = file.path; - await showDialog( - context: context, - builder: (BuildContext context) => ImageDisplay(fileName, filePath), - ); + if (context.mounted) { + await showDialog( + context: context, + builder: (BuildContext context) => ImageDisplay(fileName, filePath), + ); + } } @override diff --git a/packages/file_selector/file_selector_linux/example/lib/open_multiple_images_page.dart b/packages/file_selector/file_selector_linux/example/lib/open_multiple_images_page.dart index c8e352a5b8bd..787717cdea13 100644 --- a/packages/file_selector/file_selector_linux/example/lib/open_multiple_images_page.dart +++ b/packages/file_selector/file_selector_linux/example/lib/open_multiple_images_page.dart @@ -32,10 +32,12 @@ class OpenMultipleImagesPage extends StatelessWidget { // Operation was canceled by the user. return; } - await showDialog( - context: context, - builder: (BuildContext context) => MultipleImagesDisplay(files), - ); + if (context.mounted) { + await showDialog( + context: context, + builder: (BuildContext context) => MultipleImagesDisplay(files), + ); + } } @override diff --git a/packages/file_selector/file_selector_linux/example/lib/open_text_page.dart b/packages/file_selector/file_selector_linux/example/lib/open_text_page.dart index 4c88d7475049..97812f2b3505 100644 --- a/packages/file_selector/file_selector_linux/example/lib/open_text_page.dart +++ b/packages/file_selector/file_selector_linux/example/lib/open_text_page.dart @@ -25,10 +25,12 @@ class OpenTextPage extends StatelessWidget { final String fileName = file.name; final String fileContent = await file.readAsString(); - await showDialog( - context: context, - builder: (BuildContext context) => TextDisplay(fileName, fileContent), - ); + if (context.mounted) { + await showDialog( + context: context, + builder: (BuildContext context) => TextDisplay(fileName, fileContent), + ); + } } @override diff --git a/packages/file_selector/file_selector_macos/CHANGELOG.md b/packages/file_selector/file_selector_macos/CHANGELOG.md index d9da2eceeea9..4fdab0b73b5d 100644 --- a/packages/file_selector/file_selector_macos/CHANGELOG.md +++ b/packages/file_selector/file_selector_macos/CHANGELOG.md @@ -1,5 +1,6 @@ ## NEXT +* Updates example code for `use_build_context_synchronously` lint. * Updates minimum Flutter version to 3.0. ## 0.9.0+4 diff --git a/packages/file_selector/file_selector_macos/example/lib/get_directory_page.dart b/packages/file_selector/file_selector_macos/example/lib/get_directory_page.dart index a2a209dc9529..a3f6f6ab8798 100644 --- a/packages/file_selector/file_selector_macos/example/lib/get_directory_page.dart +++ b/packages/file_selector/file_selector_macos/example/lib/get_directory_page.dart @@ -21,10 +21,12 @@ class GetDirectoryPage extends StatelessWidget { // Operation was canceled by the user. return; } - await showDialog( - context: context, - builder: (BuildContext context) => TextDisplay(directoryPath), - ); + if (context.mounted) { + await showDialog( + context: context, + builder: (BuildContext context) => TextDisplay(directoryPath), + ); + } } @override diff --git a/packages/file_selector/file_selector_macos/example/lib/open_image_page.dart b/packages/file_selector/file_selector_macos/example/lib/open_image_page.dart index b6ada56ebb2b..9252d25f113c 100644 --- a/packages/file_selector/file_selector_macos/example/lib/open_image_page.dart +++ b/packages/file_selector/file_selector_macos/example/lib/open_image_page.dart @@ -28,10 +28,12 @@ class OpenImagePage extends StatelessWidget { final String fileName = file.name; final String filePath = file.path; - await showDialog( - context: context, - builder: (BuildContext context) => ImageDisplay(fileName, filePath), - ); + if (context.mounted) { + await showDialog( + context: context, + builder: (BuildContext context) => ImageDisplay(fileName, filePath), + ); + } } @override diff --git a/packages/file_selector/file_selector_macos/example/lib/open_multiple_images_page.dart b/packages/file_selector/file_selector_macos/example/lib/open_multiple_images_page.dart index c8e352a5b8bd..787717cdea13 100644 --- a/packages/file_selector/file_selector_macos/example/lib/open_multiple_images_page.dart +++ b/packages/file_selector/file_selector_macos/example/lib/open_multiple_images_page.dart @@ -32,10 +32,12 @@ class OpenMultipleImagesPage extends StatelessWidget { // Operation was canceled by the user. return; } - await showDialog( - context: context, - builder: (BuildContext context) => MultipleImagesDisplay(files), - ); + if (context.mounted) { + await showDialog( + context: context, + builder: (BuildContext context) => MultipleImagesDisplay(files), + ); + } } @override diff --git a/packages/file_selector/file_selector_macos/example/lib/open_text_page.dart b/packages/file_selector/file_selector_macos/example/lib/open_text_page.dart index 4c88d7475049..97812f2b3505 100644 --- a/packages/file_selector/file_selector_macos/example/lib/open_text_page.dart +++ b/packages/file_selector/file_selector_macos/example/lib/open_text_page.dart @@ -25,10 +25,12 @@ class OpenTextPage extends StatelessWidget { final String fileName = file.name; final String fileContent = await file.readAsString(); - await showDialog( - context: context, - builder: (BuildContext context) => TextDisplay(fileName, fileContent), - ); + if (context.mounted) { + await showDialog( + context: context, + builder: (BuildContext context) => TextDisplay(fileName, fileContent), + ); + } } @override diff --git a/packages/file_selector/file_selector_windows/CHANGELOG.md b/packages/file_selector/file_selector_windows/CHANGELOG.md index ee007d473d3d..1f9405d2c987 100644 --- a/packages/file_selector/file_selector_windows/CHANGELOG.md +++ b/packages/file_selector/file_selector_windows/CHANGELOG.md @@ -1,5 +1,6 @@ ## NEXT +* Updates example code for `use_build_context_synchronously` lint. * Updates minimum Flutter version to 3.0. ## 0.9.1+4 diff --git a/packages/file_selector/file_selector_windows/example/lib/get_directory_page.dart b/packages/file_selector/file_selector_windows/example/lib/get_directory_page.dart index 0699dd121541..f6390ccef20d 100644 --- a/packages/file_selector/file_selector_windows/example/lib/get_directory_page.dart +++ b/packages/file_selector/file_selector_windows/example/lib/get_directory_page.dart @@ -21,10 +21,12 @@ class GetDirectoryPage extends StatelessWidget { // Operation was canceled by the user. return; } - await showDialog( - context: context, - builder: (BuildContext context) => TextDisplay(directoryPath), - ); + if (context.mounted) { + await showDialog( + context: context, + builder: (BuildContext context) => TextDisplay(directoryPath), + ); + } } @override diff --git a/packages/file_selector/file_selector_windows/example/lib/open_image_page.dart b/packages/file_selector/file_selector_windows/example/lib/open_image_page.dart index b6ada56ebb2b..9252d25f113c 100644 --- a/packages/file_selector/file_selector_windows/example/lib/open_image_page.dart +++ b/packages/file_selector/file_selector_windows/example/lib/open_image_page.dart @@ -28,10 +28,12 @@ class OpenImagePage extends StatelessWidget { final String fileName = file.name; final String filePath = file.path; - await showDialog( - context: context, - builder: (BuildContext context) => ImageDisplay(fileName, filePath), - ); + if (context.mounted) { + await showDialog( + context: context, + builder: (BuildContext context) => ImageDisplay(fileName, filePath), + ); + } } @override diff --git a/packages/file_selector/file_selector_windows/example/lib/open_multiple_images_page.dart b/packages/file_selector/file_selector_windows/example/lib/open_multiple_images_page.dart index c8e352a5b8bd..787717cdea13 100644 --- a/packages/file_selector/file_selector_windows/example/lib/open_multiple_images_page.dart +++ b/packages/file_selector/file_selector_windows/example/lib/open_multiple_images_page.dart @@ -32,10 +32,12 @@ class OpenMultipleImagesPage extends StatelessWidget { // Operation was canceled by the user. return; } - await showDialog( - context: context, - builder: (BuildContext context) => MultipleImagesDisplay(files), - ); + if (context.mounted) { + await showDialog( + context: context, + builder: (BuildContext context) => MultipleImagesDisplay(files), + ); + } } @override diff --git a/packages/file_selector/file_selector_windows/example/lib/open_text_page.dart b/packages/file_selector/file_selector_windows/example/lib/open_text_page.dart index 4c88d7475049..97812f2b3505 100644 --- a/packages/file_selector/file_selector_windows/example/lib/open_text_page.dart +++ b/packages/file_selector/file_selector_windows/example/lib/open_text_page.dart @@ -25,10 +25,12 @@ class OpenTextPage extends StatelessWidget { final String fileName = file.name; final String fileContent = await file.readAsString(); - await showDialog( - context: context, - builder: (BuildContext context) => TextDisplay(fileName, fileContent), - ); + if (context.mounted) { + await showDialog( + context: context, + builder: (BuildContext context) => TextDisplay(fileName, fileContent), + ); + } } @override diff --git a/packages/image_picker/image_picker_windows/CHANGELOG.md b/packages/image_picker/image_picker_windows/CHANGELOG.md index 5ea844d8e0a1..e739db71363e 100644 --- a/packages/image_picker/image_picker_windows/CHANGELOG.md +++ b/packages/image_picker/image_picker_windows/CHANGELOG.md @@ -1,5 +1,6 @@ -## NEXT +## 0.1.0+4 +* Updates example code for `use_build_context_synchronously` lint. * Updates minimum Flutter version to 3.0. ## 0.1.0+3 diff --git a/packages/image_picker/image_picker_windows/example/lib/main.dart b/packages/image_picker/image_picker_windows/example/lib/main.dart index e340a185bf3d..dae45a5e2957 100644 --- a/packages/image_picker/image_picker_windows/example/lib/main.dart +++ b/packages/image_picker/image_picker_windows/example/lib/main.dart @@ -70,8 +70,8 @@ class _MyHomePageState extends State { } } - Future _handleMultiImagePicked(BuildContext? context) async { - await _displayPickImageDialog(context!, + Future _handleMultiImagePicked(BuildContext context) async { + await _displayPickImageDialog(context, (double? maxWidth, double? maxHeight, int? quality) async { try { final List? pickedFileList = await _picker.pickMultiImage( @@ -91,8 +91,8 @@ class _MyHomePageState extends State { } Future _handleSingleImagePicked( - BuildContext? context, ImageSource source) async { - await _displayPickImageDialog(context!, + BuildContext context, ImageSource source) async { + await _displayPickImageDialog(context, (double? maxWidth, double? maxHeight, int? quality) async { try { final PickedFile? pickedFile = await _picker.pickImage( @@ -113,18 +113,20 @@ class _MyHomePageState extends State { } Future _onImageButtonPressed(ImageSource source, - {BuildContext? context, bool isMultiImage = false}) async { + {required BuildContext context, bool isMultiImage = false}) async { if (_controller != null) { await _controller!.setVolume(0.0); } - if (_isVideo) { - final PickedFile? file = await _picker.pickVideo( - source: source, maxDuration: const Duration(seconds: 10)); - await _playVideo(file); - } else if (isMultiImage) { - await _handleMultiImagePicked(context); - } else { - await _handleSingleImagePicked(context, source); + if (context.mounted) { + if (_isVideo) { + final PickedFile? file = await _picker.pickVideo( + source: source, maxDuration: const Duration(seconds: 10)); + await _playVideo(file); + } else if (isMultiImage) { + await _handleMultiImagePicked(context); + } else { + await _handleSingleImagePicked(context, source); + } } } @@ -269,7 +271,7 @@ class _MyHomePageState extends State { backgroundColor: Colors.red, onPressed: () { _isVideo = true; - _onImageButtonPressed(ImageSource.gallery); + _onImageButtonPressed(ImageSource.gallery, context: context); }, heroTag: 'video0', tooltip: 'Pick Video from gallery', @@ -282,7 +284,7 @@ class _MyHomePageState extends State { backgroundColor: Colors.red, onPressed: () { _isVideo = true; - _onImageButtonPressed(ImageSource.camera); + _onImageButtonPressed(ImageSource.camera, context: context); }, heroTag: 'video1', tooltip: 'Take a Video', diff --git a/packages/image_picker/image_picker_windows/pubspec.yaml b/packages/image_picker/image_picker_windows/pubspec.yaml index e639bcbdbcd0..07fa673649de 100644 --- a/packages/image_picker/image_picker_windows/pubspec.yaml +++ b/packages/image_picker/image_picker_windows/pubspec.yaml @@ -2,7 +2,7 @@ name: image_picker_windows description: Windows platform implementation of image_picker repository: https://github.com/flutter/plugins/tree/main/packages/image_picker/image_picker_windows issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+image_picker%22 -version: 0.1.0+3 +version: 0.1.0+4 environment: sdk: ">=2.12.0 <3.0.0" diff --git a/packages/in_app_purchase/in_app_purchase/CHANGELOG.md b/packages/in_app_purchase/in_app_purchase/CHANGELOG.md index 1d76f145bee6..38355e35a849 100644 --- a/packages/in_app_purchase/in_app_purchase/CHANGELOG.md +++ b/packages/in_app_purchase/in_app_purchase/CHANGELOG.md @@ -1,5 +1,6 @@ -## NEXT +## 3.1.2 +* Updates example code for `use_build_context_synchronously` lint. * Updates minimum Flutter version to 3.0. ## 3.1.1 diff --git a/packages/in_app_purchase/in_app_purchase/example/lib/main.dart b/packages/in_app_purchase/in_app_purchase/example/lib/main.dart index 9e53b4bf8b8e..aec19fed5272 100644 --- a/packages/in_app_purchase/in_app_purchase/example/lib/main.dart +++ b/packages/in_app_purchase/in_app_purchase/example/lib/main.dart @@ -468,17 +468,19 @@ class _MyAppState extends State<_MyApp> { await androidAddition.launchPriceChangeConfirmationFlow( sku: 'purchaseId', ); - if (priceChangeConfirmationResult.responseCode == BillingResponse.ok) { - ScaffoldMessenger.of(context).showSnackBar(const SnackBar( - content: Text('Price change accepted'), - )); - } else { - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text( - priceChangeConfirmationResult.debugMessage ?? - 'Price change failed with code ${priceChangeConfirmationResult.responseCode}', - ), - )); + if (context.mounted) { + if (priceChangeConfirmationResult.responseCode == BillingResponse.ok) { + ScaffoldMessenger.of(context).showSnackBar(const SnackBar( + content: Text('Price change accepted'), + )); + } else { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text( + priceChangeConfirmationResult.debugMessage ?? + 'Price change failed with code ${priceChangeConfirmationResult.responseCode}', + ), + )); + } } } if (Platform.isIOS) { diff --git a/packages/in_app_purchase/in_app_purchase/pubspec.yaml b/packages/in_app_purchase/in_app_purchase/pubspec.yaml index 71d415c72083..598ab909fd84 100644 --- a/packages/in_app_purchase/in_app_purchase/pubspec.yaml +++ b/packages/in_app_purchase/in_app_purchase/pubspec.yaml @@ -2,7 +2,7 @@ name: in_app_purchase description: A Flutter plugin for in-app purchases. Exposes APIs for making in-app purchases through the App Store and Google Play. repository: https://github.com/flutter/plugins/tree/main/packages/in_app_purchase/in_app_purchase issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+in_app_purchase%22 -version: 3.1.1 +version: 3.1.2 environment: sdk: ">=2.12.0 <3.0.0" diff --git a/packages/webview_flutter/webview_flutter/CHANGELOG.md b/packages/webview_flutter/webview_flutter/CHANGELOG.md index a01b9f413929..6d2e860e29ec 100644 --- a/packages/webview_flutter/webview_flutter/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter/CHANGELOG.md @@ -1,3 +1,7 @@ +## 4.0.3 + +* Updates example code for `use_build_context_synchronously` lint. + ## 4.0.2 * Updates code for stricter lint checks. diff --git a/packages/webview_flutter/webview_flutter/example/lib/main.dart b/packages/webview_flutter/webview_flutter/example/lib/main.dart index 239b417c4e04..ec1ce4eef16c 100644 --- a/packages/webview_flutter/webview_flutter/example/lib/main.dart +++ b/packages/webview_flutter/webview_flutter/example/lib/main.dart @@ -180,9 +180,11 @@ Page resource error: return FloatingActionButton( onPressed: () async { final String? url = await _controller.currentUrl(); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Favorited $url')), - ); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Favorited $url')), + ); + } }, child: const Icon(Icons.favorite), ); @@ -330,25 +332,29 @@ class SampleMenu extends StatelessWidget { Future _onListCookies(BuildContext context) async { final String cookies = await webViewController .runJavaScriptReturningResult('document.cookie') as String; - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Column( - mainAxisAlignment: MainAxisAlignment.end, - mainAxisSize: MainAxisSize.min, - children: [ - const Text('Cookies:'), - _getCookieList(cookies), - ], - ), - )); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Column( + mainAxisAlignment: MainAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: [ + const Text('Cookies:'), + _getCookieList(cookies), + ], + ), + )); + } } Future _onAddToCache(BuildContext context) async { await webViewController.runJavaScript( 'caches.open("test_caches_entry"); localStorage["test_localStorage"] = "dummy_entry";', ); - ScaffoldMessenger.of(context).showSnackBar(const SnackBar( - content: Text('Added a test entry to cache.'), - )); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar(const SnackBar( + content: Text('Added a test entry to cache.'), + )); + } } Future _onListCache() { @@ -361,9 +367,11 @@ class SampleMenu extends StatelessWidget { Future _onClearCache(BuildContext context) async { await webViewController.clearCache(); await webViewController.clearLocalStorage(); - ScaffoldMessenger.of(context).showSnackBar(const SnackBar( - content: Text('Cache cleared.'), - )); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar(const SnackBar( + content: Text('Cache cleared.'), + )); + } } Future _onClearCookies(BuildContext context) async { @@ -372,9 +380,11 @@ class SampleMenu extends StatelessWidget { if (!hadCookies) { message = 'There are no cookies.'; } - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text(message), - )); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text(message), + )); + } } Future _onNavigationDelegateExample() { @@ -467,10 +477,11 @@ class NavigationControls extends StatelessWidget { if (await webViewController.canGoBack()) { await webViewController.goBack(); } else { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('No back history item')), - ); - return; + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('No back history item')), + ); + } } }, ), @@ -480,10 +491,11 @@ class NavigationControls extends StatelessWidget { if (await webViewController.canGoForward()) { await webViewController.goForward(); } else { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('No forward history item')), - ); - return; + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('No forward history item')), + ); + } } }, ), diff --git a/packages/webview_flutter/webview_flutter/pubspec.yaml b/packages/webview_flutter/webview_flutter/pubspec.yaml index 99133ddd1129..a494f9e9276c 100644 --- a/packages/webview_flutter/webview_flutter/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter/pubspec.yaml @@ -2,7 +2,7 @@ name: webview_flutter description: A Flutter plugin that provides a WebView widget on Android and iOS. repository: https://github.com/flutter/plugins/tree/main/packages/webview_flutter/webview_flutter issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22 -version: 4.0.2 +version: 4.0.3 environment: sdk: ">=2.17.0 <3.0.0" diff --git a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md index 569244b0ed3a..e1786d6cd7d0 100644 --- a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## 3.2.2 + +* Updates example code for `use_build_context_synchronously` lint. + ## 3.2.1 * Updates code for stricter lint checks. diff --git a/packages/webview_flutter/webview_flutter_android/example/lib/main.dart b/packages/webview_flutter/webview_flutter_android/example/lib/main.dart index fe6d723c058f..75f01b457b3a 100644 --- a/packages/webview_flutter/webview_flutter_android/example/lib/main.dart +++ b/packages/webview_flutter/webview_flutter_android/example/lib/main.dart @@ -164,9 +164,11 @@ Page resource error: return FloatingActionButton( onPressed: () async { final String? url = await _controller.currentUrl(); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Favorited $url')), - ); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Favorited $url')), + ); + } }, child: const Icon(Icons.favorite), ); @@ -319,25 +321,29 @@ class SampleMenu extends StatelessWidget { Future _onListCookies(BuildContext context) async { final String cookies = await webViewController .runJavaScriptReturningResult('document.cookie') as String; - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Column( - mainAxisAlignment: MainAxisAlignment.end, - mainAxisSize: MainAxisSize.min, - children: [ - const Text('Cookies:'), - _getCookieList(cookies), - ], - ), - )); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Column( + mainAxisAlignment: MainAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: [ + const Text('Cookies:'), + _getCookieList(cookies), + ], + ), + )); + } } Future _onAddToCache(BuildContext context) async { await webViewController.runJavaScript( 'caches.open("test_caches_entry"); localStorage["test_localStorage"] = "dummy_entry";', ); - ScaffoldMessenger.of(context).showSnackBar(const SnackBar( - content: Text('Added a test entry to cache.'), - )); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar(const SnackBar( + content: Text('Added a test entry to cache.'), + )); + } } Future _onListCache() { @@ -350,9 +356,11 @@ class SampleMenu extends StatelessWidget { Future _onClearCache(BuildContext context) async { await webViewController.clearCache(); await webViewController.clearLocalStorage(); - ScaffoldMessenger.of(context).showSnackBar(const SnackBar( - content: Text('Cache cleared.'), - )); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar(const SnackBar( + content: Text('Cache cleared.'), + )); + } } Future _onClearCookies(BuildContext context) async { @@ -361,9 +369,11 @@ class SampleMenu extends StatelessWidget { if (!hadCookies) { message = 'There are no cookies.'; } - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text(message), - )); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text(message), + )); + } } Future _onNavigationDelegateExample() { @@ -462,10 +472,11 @@ class NavigationControls extends StatelessWidget { if (await webViewController.canGoBack()) { await webViewController.goBack(); } else { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('No back history item')), - ); - return; + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('No back history item')), + ); + } } }, ), @@ -475,10 +486,11 @@ class NavigationControls extends StatelessWidget { if (await webViewController.canGoForward()) { await webViewController.goForward(); } else { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('No forward history item')), - ); - return; + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('No forward history item')), + ); + } } }, ), diff --git a/packages/webview_flutter/webview_flutter_android/pubspec.yaml b/packages/webview_flutter/webview_flutter_android/pubspec.yaml index eb761cca9104..81255dfa0f93 100644 --- a/packages/webview_flutter/webview_flutter_android/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_android/pubspec.yaml @@ -2,7 +2,7 @@ name: webview_flutter_android description: A Flutter plugin that provides a WebView widget on Android. repository: https://github.com/flutter/plugins/tree/main/packages/webview_flutter/webview_flutter_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22 -version: 3.2.1 +version: 3.2.2 environment: sdk: ">=2.17.0 <3.0.0" diff --git a/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md b/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md index b4ad2a16b425..a9cb87f57c65 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md @@ -1,3 +1,7 @@ +## 3.0.3 + +* Updates example code for `use_build_context_synchronously` lint. + ## 3.0.2 * Updates code for stricter lint checks. diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/lib/main.dart b/packages/webview_flutter/webview_flutter_wkwebview/example/lib/main.dart index 84aced1b75e8..aef7ece0c2e3 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/lib/main.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/lib/main.dart @@ -165,9 +165,11 @@ Page resource error: return FloatingActionButton( onPressed: () async { final String? url = await _controller.currentUrl(); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Favorited $url')), - ); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Favorited $url')), + ); + } }, child: const Icon(Icons.favorite), ); @@ -320,25 +322,29 @@ class SampleMenu extends StatelessWidget { Future _onListCookies(BuildContext context) async { final String cookies = await webViewController .runJavaScriptReturningResult('document.cookie') as String; - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Column( - mainAxisAlignment: MainAxisAlignment.end, - mainAxisSize: MainAxisSize.min, - children: [ - const Text('Cookies:'), - _getCookieList(cookies), - ], - ), - )); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Column( + mainAxisAlignment: MainAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: [ + const Text('Cookies:'), + _getCookieList(cookies), + ], + ), + )); + } } Future _onAddToCache(BuildContext context) async { await webViewController.runJavaScript( 'caches.open("test_caches_entry"); localStorage["test_localStorage"] = "dummy_entry";', ); - ScaffoldMessenger.of(context).showSnackBar(const SnackBar( - content: Text('Added a test entry to cache.'), - )); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar(const SnackBar( + content: Text('Added a test entry to cache.'), + )); + } } Future _onListCache() { @@ -351,9 +357,11 @@ class SampleMenu extends StatelessWidget { Future _onClearCache(BuildContext context) async { await webViewController.clearCache(); await webViewController.clearLocalStorage(); - ScaffoldMessenger.of(context).showSnackBar(const SnackBar( - content: Text('Cache cleared.'), - )); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar(const SnackBar( + content: Text('Cache cleared.'), + )); + } } Future _onClearCookies(BuildContext context) async { @@ -362,9 +370,11 @@ class SampleMenu extends StatelessWidget { if (!hadCookies) { message = 'There are no cookies.'; } - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text(message), - )); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text(message), + )); + } } Future _onNavigationDelegateExample() { @@ -463,10 +473,11 @@ class NavigationControls extends StatelessWidget { if (await webViewController.canGoBack()) { await webViewController.goBack(); } else { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('No back history item')), - ); - return; + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('No back history item')), + ); + } } }, ), @@ -476,10 +487,11 @@ class NavigationControls extends StatelessWidget { if (await webViewController.canGoForward()) { await webViewController.goForward(); } else { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('No forward history item')), - ); - return; + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('No forward history item')), + ); + } } }, ), diff --git a/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml b/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml index 85440f6e3dfc..c41bce18cae6 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml @@ -2,7 +2,7 @@ name: webview_flutter_wkwebview description: A Flutter plugin that provides a WebView widget based on Apple's WKWebView control. repository: https://github.com/flutter/plugins/tree/main/packages/webview_flutter/webview_flutter_wkwebview issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22 -version: 3.0.2 +version: 3.0.3 environment: sdk: ">=2.17.0 <3.0.0" From bc0174fd54efaafcfd6b38b0f8282eec3ae55631 Mon Sep 17 00:00:00 2001 From: stuartmorgan Date: Wed, 25 Jan 2023 09:25:07 -0800 Subject: [PATCH 035/130] [path_provider] Fix iOS `getApplicationSupportDirectory` regression (#7026) When switching iOS to share the macOS implementation, the application support path for iOS accidentally changed because I forgot the macOS implementation added an extra subdirectory to it. This fixes that regression by returning iOS to the path `path_provider_ios` used. Fixes https://github.com/flutter/flutter/issues/119133 --- .../path_provider_foundation/CHANGELOG.md | 4 ++-- .../darwin/Classes/PathProviderPlugin.swift | 7 +++++++ .../{Tests => RunnerTests}/RunnerTests.swift | 16 ++++++++++++++-- .../example/ios/Runner.xcodeproj/project.pbxproj | 14 +++++++------- .../macos/Runner.xcodeproj/project.pbxproj | 2 +- .../path_provider_foundation/pubspec.yaml | 4 ++-- 6 files changed, 33 insertions(+), 14 deletions(-) rename packages/path_provider/path_provider_foundation/darwin/{Tests => RunnerTests}/RunnerTests.swift (80%) diff --git a/packages/path_provider/path_provider_foundation/CHANGELOG.md b/packages/path_provider/path_provider_foundation/CHANGELOG.md index a65177eb2cde..17e45d37a2ba 100644 --- a/packages/path_provider/path_provider_foundation/CHANGELOG.md +++ b/packages/path_provider/path_provider_foundation/CHANGELOG.md @@ -1,6 +1,6 @@ -## NEXT +## 2.1.1 -* Updates minimum Flutter version to 3.0. +* Fixes a regression in the path retured by `getApplicationSupportDirectory` on iOS. ## 2.1.0 diff --git a/packages/path_provider/path_provider_foundation/darwin/Classes/PathProviderPlugin.swift b/packages/path_provider/path_provider_foundation/darwin/Classes/PathProviderPlugin.swift index 770bcb31c529..af043090f545 100644 --- a/packages/path_provider/path_provider_foundation/darwin/Classes/PathProviderPlugin.swift +++ b/packages/path_provider/path_provider_foundation/darwin/Classes/PathProviderPlugin.swift @@ -24,12 +24,19 @@ public class PathProviderPlugin: NSObject, FlutterPlugin, PathProviderApi { func getDirectoryPath(type: DirectoryType) -> String? { var path = getDirectory(ofType: fileManagerDirectoryForType(type)) + #if os(macOS) + // In a non-sandboxed app, this is a shared directory where applications are + // expected to use its bundle ID as a subdirectory. (For non-sandboxed apps, + // adding the extra path is harmless). + // This is not done for iOS, for compatibility with older versions of the + // plugin. if type == .applicationSupport { if let basePath = path { let basePathURL = URL.init(fileURLWithPath: basePath) path = basePathURL.appendingPathComponent(Bundle.main.bundleIdentifier!).path } } + #endif return path } } diff --git a/packages/path_provider/path_provider_foundation/darwin/Tests/RunnerTests.swift b/packages/path_provider/path_provider_foundation/darwin/RunnerTests/RunnerTests.swift similarity index 80% rename from packages/path_provider/path_provider_foundation/darwin/Tests/RunnerTests.swift rename to packages/path_provider/path_provider_foundation/darwin/RunnerTests/RunnerTests.swift index 1f3e790ca1eb..99a56f2bfebf 100644 --- a/packages/path_provider/path_provider_foundation/darwin/Tests/RunnerTests.swift +++ b/packages/path_provider/path_provider_foundation/darwin/RunnerTests/RunnerTests.swift @@ -39,8 +39,19 @@ class RunnerTests: XCTestCase { func testGetApplicationSupportDirectory() throws { let plugin = PathProviderPlugin() let path = plugin.getDirectoryPath(type: .applicationSupport) - // The application support directory path should be the system application support - // path with an added subdirectory based on the app name. +#if os(iOS) + // On iOS, the application support directory path should be just the system application + // support path. + XCTAssertEqual( + path, + NSSearchPathForDirectoriesInDomains( + FileManager.SearchPathDirectory.applicationSupportDirectory, + FileManager.SearchPathDomainMask.userDomainMask, + true + ).first) +#else + // On macOS, the application support directory path should be the system application + // support path with an added subdirectory based on the app name. XCTAssert( path!.hasPrefix( NSSearchPathForDirectoriesInDomains( @@ -49,6 +60,7 @@ class RunnerTests: XCTestCase { true ).first!)) XCTAssert(path!.hasSuffix("Example")) +#endif } func testGetLibraryDirectory() throws { diff --git a/packages/path_provider/path_provider_foundation/example/ios/Runner.xcodeproj/project.pbxproj b/packages/path_provider/path_provider_foundation/example/ios/Runner.xcodeproj/project.pbxproj index 866a166bbe45..70cdc7657d6d 100644 --- a/packages/path_provider/path_provider_foundation/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/path_provider/path_provider_foundation/example/ios/Runner.xcodeproj/project.pbxproj @@ -8,7 +8,7 @@ /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; - 3380327729784D96002D32AE /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3380327629784D96002D32AE /* RunnerTests.swift */; }; + 33258D7929818305006BAA98 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33258D7729818302006BAA98 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 569E86265D93B926F433B2DF /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 479D5DD53D431F6BBABA2E43 /* Pods_Runner.framework */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; @@ -45,8 +45,8 @@ 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 = ""; }; 1E28C831B7D8EA9408BFB69A /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + 33258D7729818302006BAA98 /* RunnerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = RunnerTests.swift; path = ../../darwin/RunnerTests/RunnerTests.swift; sourceTree = ""; }; 3380327429784D96002D32AE /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 3380327629784D96002D32AE /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = RunnerTests.swift; path = ../../../darwin/Tests/RunnerTests.swift; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 479D5DD53D431F6BBABA2E43 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 5DB8EF5A2759054360D79B8D /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; @@ -87,12 +87,12 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 3380327529784D96002D32AE /* RunnerTests */ = { + 33258D76298182CC006BAA98 /* RunnerTests */ = { isa = PBXGroup; children = ( - 3380327629784D96002D32AE /* RunnerTests.swift */, + 33258D7729818302006BAA98 /* RunnerTests.swift */, ); - path = RunnerTests; + name = RunnerTests; sourceTree = ""; }; 9740EEB11CF90186004384FC /* Flutter */ = { @@ -111,7 +111,7 @@ children = ( 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, - 3380327529784D96002D32AE /* RunnerTests */, + 33258D76298182CC006BAA98 /* RunnerTests */, 97C146EF1CF9000F007C117D /* Products */, E1C876D20454FC3A1ED7F7E5 /* Pods */, C72F144CE69E83C4574EB334 /* Frameworks */, @@ -365,7 +365,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 3380327729784D96002D32AE /* RunnerTests.swift in Sources */, + 33258D7929818305006BAA98 /* RunnerTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/packages/path_provider/path_provider_foundation/example/macos/Runner.xcodeproj/project.pbxproj b/packages/path_provider/path_provider_foundation/example/macos/Runner.xcodeproj/project.pbxproj index 54b137e735f2..5abc18a86297 100644 --- a/packages/path_provider/path_provider_foundation/example/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/path_provider/path_provider_foundation/example/macos/Runner.xcodeproj/project.pbxproj @@ -82,7 +82,7 @@ 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; 33EBD3A726728EA70013E557 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 33EBD3A926728EA70013E557 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ../../../darwin/Tests/RunnerTests.swift; sourceTree = ""; }; + 33EBD3A926728EA70013E557 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ../../../darwin/RunnerTests/RunnerTests.swift; sourceTree = ""; }; 33EBD3AB26728EA70013E557 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 46139048DB9F59D473B61B5E /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; diff --git a/packages/path_provider/path_provider_foundation/pubspec.yaml b/packages/path_provider/path_provider_foundation/pubspec.yaml index a0d14c96eea0..9ceb115ccfe1 100644 --- a/packages/path_provider/path_provider_foundation/pubspec.yaml +++ b/packages/path_provider/path_provider_foundation/pubspec.yaml @@ -2,11 +2,11 @@ name: path_provider_foundation description: iOS and macOS implementation of the path_provider plugin repository: https://github.com/flutter/plugins/tree/main/packages/path_provider/path_provider_foundation issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+path_provider%22 -version: 2.1.0 +version: 2.1.1 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=3.0.0" + flutter: ">=2.10.0" flutter: plugin: From 15d799b893679336fe42b59b0c4b3ca5f5d40ec4 Mon Sep 17 00:00:00 2001 From: stuartmorgan Date: Wed, 25 Jan 2023 09:28:05 -0800 Subject: [PATCH 036/130] [url_launcher] Convert Windows to Pigeon (#6991) * Initial definition matching current API * Rename, autoformat * Update native implementation and unit tests * Update Dart; remove unnecessary Pigeon test API * Version bump * autoformat * Adjust mock API setup * Improve comment --- .../url_launcher_windows/CHANGELOG.md | 3 +- .../lib/src/messages.g.dart | 71 +++++++ .../lib/url_launcher_windows.dart | 34 ++- .../pigeons/copyright.txt | 3 + .../pigeons/messages.dart | 18 ++ .../url_launcher_windows/pubspec.yaml | 3 +- .../test/url_launcher_windows_test.dart | 197 +++++++----------- .../windows/CMakeLists.txt | 2 + .../windows/messages.g.cpp | 113 ++++++++++ .../url_launcher_windows/windows/messages.g.h | 86 ++++++++ .../windows/system_apis.cpp | 4 +- .../windows/system_apis.h | 4 +- .../test/url_launcher_windows_test.cpp | 82 ++------ .../windows/url_launcher_plugin.cpp | 53 +---- .../windows/url_launcher_plugin.h | 19 +- .../windows/url_launcher_windows.cpp | 2 +- 16 files changed, 432 insertions(+), 262 deletions(-) create mode 100644 packages/url_launcher/url_launcher_windows/lib/src/messages.g.dart create mode 100644 packages/url_launcher/url_launcher_windows/pigeons/copyright.txt create mode 100644 packages/url_launcher/url_launcher_windows/pigeons/messages.dart create mode 100644 packages/url_launcher/url_launcher_windows/windows/messages.g.cpp create mode 100644 packages/url_launcher/url_launcher_windows/windows/messages.g.h diff --git a/packages/url_launcher/url_launcher_windows/CHANGELOG.md b/packages/url_launcher/url_launcher_windows/CHANGELOG.md index 07a5ef3ee8c0..abb3ab10db57 100644 --- a/packages/url_launcher/url_launcher_windows/CHANGELOG.md +++ b/packages/url_launcher/url_launcher_windows/CHANGELOG.md @@ -1,5 +1,6 @@ -## NEXT +## 3.0.3 +* Converts internal implentation to Pigeon. * Updates minimum Flutter version to 3.0. ## 3.0.2 diff --git a/packages/url_launcher/url_launcher_windows/lib/src/messages.g.dart b/packages/url_launcher/url_launcher_windows/lib/src/messages.g.dart new file mode 100644 index 000000000000..a1d46c11267d --- /dev/null +++ b/packages/url_launcher/url_launcher_windows/lib/src/messages.g.dart @@ -0,0 +1,71 @@ +// Copyright 2013 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. +// Autogenerated from Pigeon (v5.0.1), do not edit directly. +// See also: https://pub.dev/packages/pigeon +// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import +import 'dart:async'; +import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; + +import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; +import 'package:flutter/services.dart'; + +class UrlLauncherApi { + /// Constructor for [UrlLauncherApi]. The [binaryMessenger] named argument is + /// available for dependency injection. If it is left null, the default + /// BinaryMessenger will be used which routes to the host platform. + UrlLauncherApi({BinaryMessenger? binaryMessenger}) + : _binaryMessenger = binaryMessenger; + final BinaryMessenger? _binaryMessenger; + + static const MessageCodec codec = StandardMessageCodec(); + + Future canLaunchUrl(String arg_url) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.UrlLauncherApi.canLaunchUrl', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send([arg_url]) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else if (replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (replyList[0] as bool?)!; + } + } + + Future launchUrl(String arg_url) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.UrlLauncherApi.launchUrl', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send([arg_url]) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else { + return; + } + } +} diff --git a/packages/url_launcher/url_launcher_windows/lib/url_launcher_windows.dart b/packages/url_launcher/url_launcher_windows/lib/url_launcher_windows.dart index b0ee8cb1a0b4..41c403e56f8e 100644 --- a/packages/url_launcher/url_launcher_windows/lib/url_launcher_windows.dart +++ b/packages/url_launcher/url_launcher_windows/lib/url_launcher_windows.dart @@ -2,17 +2,21 @@ // 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/foundation.dart'; import 'package:url_launcher_platform_interface/link.dart'; import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; -const MethodChannel _channel = - MethodChannel('plugins.flutter.io/url_launcher_windows'); +import 'src/messages.g.dart'; /// An implementation of [UrlLauncherPlatform] for Windows. class UrlLauncherWindows extends UrlLauncherPlatform { + /// Creates a new plugin implementation instance. + UrlLauncherWindows({ + @visibleForTesting UrlLauncherApi? api, + }) : _hostApi = api ?? UrlLauncherApi(); + + final UrlLauncherApi _hostApi; + /// Registers this class as the default instance of [UrlLauncherPlatform]. static void registerWith() { UrlLauncherPlatform.instance = UrlLauncherWindows(); @@ -23,10 +27,7 @@ class UrlLauncherWindows extends UrlLauncherPlatform { @override Future canLaunch(String url) { - return _channel.invokeMethod( - 'canLaunch', - {'url': url}, - ).then((bool? value) => value ?? false); + return _hostApi.canLaunchUrl(url); } @override @@ -39,16 +40,9 @@ class UrlLauncherWindows extends UrlLauncherPlatform { required bool universalLinksOnly, required Map headers, String? webOnlyWindowName, - }) { - return _channel.invokeMethod( - 'launch', - { - 'url': url, - 'enableJavaScript': enableJavaScript, - 'enableDomStorage': enableDomStorage, - 'universalLinksOnly': universalLinksOnly, - 'headers': headers, - }, - ).then((bool? value) => value ?? false); + }) async { + await _hostApi.launchUrl(url); + // Failure is handled via a PlatformException from `launchUrl`. + return true; } } diff --git a/packages/url_launcher/url_launcher_windows/pigeons/copyright.txt b/packages/url_launcher/url_launcher_windows/pigeons/copyright.txt new file mode 100644 index 000000000000..1236b63caf3a --- /dev/null +++ b/packages/url_launcher/url_launcher_windows/pigeons/copyright.txt @@ -0,0 +1,3 @@ +Copyright 2013 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. diff --git a/packages/url_launcher/url_launcher_windows/pigeons/messages.dart b/packages/url_launcher/url_launcher_windows/pigeons/messages.dart new file mode 100644 index 000000000000..9607cdffc686 --- /dev/null +++ b/packages/url_launcher/url_launcher_windows/pigeons/messages.dart @@ -0,0 +1,18 @@ +// Copyright 2013 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. + +import 'package:pigeon/pigeon.dart'; + +@ConfigurePigeon(PigeonOptions( + dartOut: 'lib/src/messages.g.dart', + cppOptions: CppOptions(namespace: 'url_launcher_windows'), + cppHeaderOut: 'windows/messages.g.h', + cppSourceOut: 'windows/messages.g.cpp', + copyrightHeader: 'pigeons/copyright.txt', +)) +@HostApi(dartHostTestHandler: 'TestUrlLauncherApi') +abstract class UrlLauncherApi { + bool canLaunchUrl(String url); + void launchUrl(String url); +} diff --git a/packages/url_launcher/url_launcher_windows/pubspec.yaml b/packages/url_launcher/url_launcher_windows/pubspec.yaml index 63ca778f2af9..de4f5edd69eb 100644 --- a/packages/url_launcher/url_launcher_windows/pubspec.yaml +++ b/packages/url_launcher/url_launcher_windows/pubspec.yaml @@ -2,7 +2,7 @@ name: url_launcher_windows description: Windows implementation of the url_launcher plugin. repository: https://github.com/flutter/plugins/tree/main/packages/url_launcher/url_launcher_windows issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+url_launcher%22 -version: 3.0.2 +version: 3.0.3 environment: sdk: ">=2.12.0 <3.0.0" @@ -24,4 +24,5 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter + pigeon: ^5.0.1 test: ^1.16.3 diff --git a/packages/url_launcher/url_launcher_windows/test/url_launcher_windows_test.dart b/packages/url_launcher/url_launcher_windows/test/url_launcher_windows_test.dart index 8b55b29bb530..7f48f64fa92c 100644 --- a/packages/url_launcher/url_launcher_windows/test/url_launcher_windows_test.dart +++ b/packages/url_launcher/url_launcher_windows/test/url_launcher_windows_test.dart @@ -5,140 +5,101 @@ import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; +import 'package:url_launcher_windows/src/messages.g.dart'; import 'package:url_launcher_windows/url_launcher_windows.dart'; void main() { - TestWidgetsFlutterBinding.ensureInitialized(); - - group('$UrlLauncherWindows', () { - const MethodChannel channel = - MethodChannel('plugins.flutter.io/url_launcher_windows'); - final List log = []; - channel.setMockMethodCallHandler((MethodCall methodCall) async { - log.add(methodCall); - - // Return null explicitly instead of relying on the implicit null - // returned by the method channel if no return statement is specified. - return null; - }); + late _FakeUrlLauncherApi api; + late UrlLauncherWindows plugin; - test('registers instance', () { - UrlLauncherWindows.registerWith(); - expect(UrlLauncherPlatform.instance, isA()); - }); + setUp(() { + api = _FakeUrlLauncherApi(); + plugin = UrlLauncherWindows(api: api); + }); - tearDown(() { - log.clear(); - }); + test('registers instance', () { + UrlLauncherWindows.registerWith(); + expect(UrlLauncherPlatform.instance, isA()); + }); - test('canLaunch', () async { - final UrlLauncherWindows launcher = UrlLauncherWindows(); - await launcher.canLaunch('http://example.com/'); - expect( - log, - [ - isMethodCall('canLaunch', arguments: { - 'url': 'http://example.com/', - }) - ], - ); - }); + group('canLaunch', () { + test('handles true', () async { + api.canLaunch = true; - test('canLaunch should return false if platform returns null', () async { - final UrlLauncherWindows launcher = UrlLauncherWindows(); - final bool canLaunch = await launcher.canLaunch('http://example.com/'); + final bool result = await plugin.canLaunch('http://example.com/'); - expect(canLaunch, false); + expect(result, isTrue); + expect(api.argument, 'http://example.com/'); }); - test('launch', () async { - final UrlLauncherWindows launcher = UrlLauncherWindows(); - await launcher.launch( - 'http://example.com/', - useSafariVC: true, - useWebView: false, - enableJavaScript: false, - enableDomStorage: false, - universalLinksOnly: false, - headers: const {}, - ); - expect( - log, - [ - isMethodCall('launch', arguments: { - 'url': 'http://example.com/', - 'enableJavaScript': false, - 'enableDomStorage': false, - 'universalLinksOnly': false, - 'headers': {}, - }) - ], - ); - }); + test('handles false', () async { + api.canLaunch = false; - test('launch with headers', () async { - final UrlLauncherWindows launcher = UrlLauncherWindows(); - await launcher.launch( - 'http://example.com/', - useSafariVC: true, - useWebView: false, - enableJavaScript: false, - enableDomStorage: false, - universalLinksOnly: false, - headers: const {'key': 'value'}, - ); - expect( - log, - [ - isMethodCall('launch', arguments: { - 'url': 'http://example.com/', - 'enableJavaScript': false, - 'enableDomStorage': false, - 'universalLinksOnly': false, - 'headers': {'key': 'value'}, - }) - ], - ); + final bool result = await plugin.canLaunch('http://example.com/'); + + expect(result, isFalse); + expect(api.argument, 'http://example.com/'); }); + }); + + group('launch', () { + test('handles success', () async { + api.canLaunch = true; - test('launch universal links only', () async { - final UrlLauncherWindows launcher = UrlLauncherWindows(); - await launcher.launch( - 'http://example.com/', - useSafariVC: false, - useWebView: false, - enableJavaScript: false, - enableDomStorage: false, - universalLinksOnly: true, - headers: const {}, - ); expect( - log, - [ - isMethodCall('launch', arguments: { - 'url': 'http://example.com/', - 'enableJavaScript': false, - 'enableDomStorage': false, - 'universalLinksOnly': true, - 'headers': {}, - }) - ], - ); + plugin.launch( + 'http://example.com/', + useSafariVC: true, + useWebView: false, + enableJavaScript: false, + enableDomStorage: false, + universalLinksOnly: false, + headers: const {}, + ), + completes); + expect(api.argument, 'http://example.com/'); }); - test('launch should return false if platform returns null', () async { - final UrlLauncherWindows launcher = UrlLauncherWindows(); - final bool launched = await launcher.launch( - 'http://example.com/', - useSafariVC: true, - useWebView: false, - enableJavaScript: false, - enableDomStorage: false, - universalLinksOnly: false, - headers: const {}, - ); - - expect(launched, false); + test('handles failure', () async { + api.canLaunch = false; + + await expectLater( + plugin.launch( + 'http://example.com/', + useSafariVC: true, + useWebView: false, + enableJavaScript: false, + enableDomStorage: false, + universalLinksOnly: false, + headers: const {}, + ), + throwsA(isA())); + expect(api.argument, 'http://example.com/'); }); }); } + +class _FakeUrlLauncherApi implements UrlLauncherApi { + /// The argument that was passed to an API call. + String? argument; + + /// Controls the behavior of the fake implementations. + /// + /// - [canLaunchUrl] returns this value. + /// - [launchUrl] throws if this is false. + bool canLaunch = false; + + @override + Future canLaunchUrl(String url) async { + argument = url; + return canLaunch; + } + + @override + Future launchUrl(String url) async { + argument = url; + if (!canLaunch) { + throw PlatformException(code: 'Failed'); + } + } +} diff --git a/packages/url_launcher/url_launcher_windows/windows/CMakeLists.txt b/packages/url_launcher/url_launcher_windows/windows/CMakeLists.txt index a4185acff6a1..a34bcb3d35da 100644 --- a/packages/url_launcher/url_launcher_windows/windows/CMakeLists.txt +++ b/packages/url_launcher/url_launcher_windows/windows/CMakeLists.txt @@ -5,6 +5,8 @@ project(${PROJECT_NAME} LANGUAGES CXX) set(PLUGIN_NAME "${PROJECT_NAME}_plugin") list(APPEND PLUGIN_SOURCES + "messages.g.cpp" + "messages.g.h" "system_apis.cpp" "system_apis.h" "url_launcher_plugin.cpp" diff --git a/packages/url_launcher/url_launcher_windows/windows/messages.g.cpp b/packages/url_launcher/url_launcher_windows/windows/messages.g.cpp new file mode 100644 index 000000000000..eb1cf792931f --- /dev/null +++ b/packages/url_launcher/url_launcher_windows/windows/messages.g.cpp @@ -0,0 +1,113 @@ +// Copyright 2013 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. +// Autogenerated from Pigeon (v5.0.1), do not edit directly. +// See also: https://pub.dev/packages/pigeon + +#undef _HAS_EXCEPTIONS + +#include "messages.g.h" + +#include +#include +#include +#include + +#include +#include +#include + +namespace url_launcher_windows { + +/// The codec used by UrlLauncherApi. +const flutter::StandardMessageCodec& UrlLauncherApi::GetCodec() { + return flutter::StandardMessageCodec::GetInstance( + &flutter::StandardCodecSerializer::GetInstance()); +} + +// Sets up an instance of `UrlLauncherApi` to handle messages through the +// `binary_messenger`. +void UrlLauncherApi::SetUp(flutter::BinaryMessenger* binary_messenger, + UrlLauncherApi* api) { + { + auto channel = + std::make_unique>( + binary_messenger, "dev.flutter.pigeon.UrlLauncherApi.canLaunchUrl", + &GetCodec()); + if (api != nullptr) { + channel->SetMessageHandler( + [api](const flutter::EncodableValue& message, + const flutter::MessageReply& reply) { + try { + const auto& args = std::get(message); + const auto& encodable_url_arg = args.at(0); + if (encodable_url_arg.IsNull()) { + reply(WrapError("url_arg unexpectedly null.")); + return; + } + const auto& url_arg = std::get(encodable_url_arg); + ErrorOr output = api->CanLaunchUrl(url_arg); + if (output.has_error()) { + reply(WrapError(output.error())); + return; + } + flutter::EncodableList wrapped; + wrapped.push_back( + flutter::EncodableValue(std::move(output).TakeValue())); + reply(flutter::EncodableValue(std::move(wrapped))); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel->SetMessageHandler(nullptr); + } + } + { + auto channel = + std::make_unique>( + binary_messenger, "dev.flutter.pigeon.UrlLauncherApi.launchUrl", + &GetCodec()); + if (api != nullptr) { + channel->SetMessageHandler( + [api](const flutter::EncodableValue& message, + const flutter::MessageReply& reply) { + try { + const auto& args = std::get(message); + const auto& encodable_url_arg = args.at(0); + if (encodable_url_arg.IsNull()) { + reply(WrapError("url_arg unexpectedly null.")); + return; + } + const auto& url_arg = std::get(encodable_url_arg); + std::optional output = api->LaunchUrl(url_arg); + if (output.has_value()) { + reply(WrapError(output.value())); + return; + } + flutter::EncodableList wrapped; + wrapped.push_back(flutter::EncodableValue()); + reply(flutter::EncodableValue(std::move(wrapped))); + } catch (const std::exception& exception) { + reply(WrapError(exception.what())); + } + }); + } else { + channel->SetMessageHandler(nullptr); + } + } +} + +flutter::EncodableValue UrlLauncherApi::WrapError( + std::string_view error_message) { + return flutter::EncodableValue(flutter::EncodableList{ + flutter::EncodableValue(std::string(error_message)), + flutter::EncodableValue("Error"), flutter::EncodableValue()}); +} +flutter::EncodableValue UrlLauncherApi::WrapError(const FlutterError& error) { + return flutter::EncodableValue(flutter::EncodableList{ + flutter::EncodableValue(error.message()), + flutter::EncodableValue(error.code()), error.details()}); +} + +} // namespace url_launcher_windows diff --git a/packages/url_launcher/url_launcher_windows/windows/messages.g.h b/packages/url_launcher/url_launcher_windows/windows/messages.g.h new file mode 100644 index 000000000000..cb8e95f8d065 --- /dev/null +++ b/packages/url_launcher/url_launcher_windows/windows/messages.g.h @@ -0,0 +1,86 @@ +// Copyright 2013 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. +// Autogenerated from Pigeon (v5.0.1), do not edit directly. +// See also: https://pub.dev/packages/pigeon + +#ifndef PIGEON_H_ +#define PIGEON_H_ +#include +#include +#include +#include + +#include +#include +#include + +namespace url_launcher_windows { + +// Generated class from Pigeon. + +class FlutterError { + public: + explicit FlutterError(const std::string& code) : code_(code) {} + explicit FlutterError(const std::string& code, const std::string& message) + : code_(code), message_(message) {} + explicit FlutterError(const std::string& code, const std::string& message, + const flutter::EncodableValue& details) + : code_(code), message_(message), details_(details) {} + + const std::string& code() const { return code_; } + const std::string& message() const { return message_; } + const flutter::EncodableValue& details() const { return details_; } + + private: + std::string code_; + std::string message_; + flutter::EncodableValue details_; +}; + +template +class ErrorOr { + public: + ErrorOr(const T& rhs) { new (&v_) T(rhs); } + ErrorOr(const T&& rhs) { v_ = std::move(rhs); } + ErrorOr(const FlutterError& rhs) { new (&v_) FlutterError(rhs); } + ErrorOr(const FlutterError&& rhs) { v_ = std::move(rhs); } + + bool has_error() const { return std::holds_alternative(v_); } + const T& value() const { return std::get(v_); }; + const FlutterError& error() const { return std::get(v_); }; + + private: + friend class UrlLauncherApi; + ErrorOr() = default; + T TakeValue() && { return std::get(std::move(v_)); } + + std::variant v_; +}; + +// Generated interface from Pigeon that represents a handler of messages from +// Flutter. +class UrlLauncherApi { + public: + UrlLauncherApi(const UrlLauncherApi&) = delete; + UrlLauncherApi& operator=(const UrlLauncherApi&) = delete; + virtual ~UrlLauncherApi(){}; + virtual ErrorOr CanLaunchUrl(const std::string& url) = 0; + virtual std::optional LaunchUrl(const std::string& url) = 0; + + // The codec used by UrlLauncherApi. + static const flutter::StandardMessageCodec& GetCodec(); + // Sets up an instance of `UrlLauncherApi` to handle messages through the + // `binary_messenger`. + static void SetUp(flutter::BinaryMessenger* binary_messenger, + UrlLauncherApi* api); + static flutter::EncodableValue WrapError(std::string_view error_message); + static flutter::EncodableValue WrapError(const FlutterError& error); + + protected: + UrlLauncherApi() = default; +}; + +} // namespace url_launcher_windows + +#endif // PIGEON_H_ diff --git a/packages/url_launcher/url_launcher_windows/windows/system_apis.cpp b/packages/url_launcher/url_launcher_windows/windows/system_apis.cpp index abd690b6e47f..cde95ee1b399 100644 --- a/packages/url_launcher/url_launcher_windows/windows/system_apis.cpp +++ b/packages/url_launcher/url_launcher_windows/windows/system_apis.cpp @@ -5,7 +5,7 @@ #include -namespace url_launcher_plugin { +namespace url_launcher_windows { SystemApis::SystemApis() {} @@ -35,4 +35,4 @@ HINSTANCE SystemApisImpl::ShellExecuteW(HWND hwnd, LPCWSTR operation, show_flags); } -} // namespace url_launcher_plugin +} // namespace url_launcher_windows diff --git a/packages/url_launcher/url_launcher_windows/windows/system_apis.h b/packages/url_launcher/url_launcher_windows/windows/system_apis.h index 7b56704d8e04..c56c4100180b 100644 --- a/packages/url_launcher/url_launcher_windows/windows/system_apis.h +++ b/packages/url_launcher/url_launcher_windows/windows/system_apis.h @@ -3,7 +3,7 @@ // found in the LICENSE file. #include -namespace url_launcher_plugin { +namespace url_launcher_windows { // An interface wrapping system APIs used by the plugin, for mocking. class SystemApis { @@ -53,4 +53,4 @@ class SystemApisImpl : public SystemApis { int show_flags); }; -} // namespace url_launcher_plugin +} // namespace url_launcher_windows diff --git a/packages/url_launcher/url_launcher_windows/windows/test/url_launcher_windows_test.cpp b/packages/url_launcher/url_launcher_windows/windows/test/url_launcher_windows_test.cpp index 191d51a0caa8..9dd2be5347b5 100644 --- a/packages/url_launcher/url_launcher_windows/windows/test/url_launcher_windows_test.cpp +++ b/packages/url_launcher/url_launcher_windows/windows/test/url_launcher_windows_test.cpp @@ -9,11 +9,13 @@ #include #include +#include #include +#include "messages.g.h" #include "url_launcher_plugin.h" -namespace url_launcher_plugin { +namespace url_launcher_windows { namespace test { namespace { @@ -42,30 +44,10 @@ class MockSystemApis : public SystemApis { (override)); }; -class MockMethodResult : public flutter::MethodResult<> { - public: - MOCK_METHOD(void, SuccessInternal, (const EncodableValue* result), - (override)); - MOCK_METHOD(void, ErrorInternal, - (const std::string& error_code, const std::string& error_message, - const EncodableValue* details), - (override)); - MOCK_METHOD(void, NotImplementedInternal, (), (override)); -}; - -std::unique_ptr CreateArgumentsWithUrl(const std::string& url) { - EncodableMap args = { - {EncodableValue("url"), EncodableValue(url)}, - }; - return std::make_unique(args); -} - } // namespace TEST(UrlLauncherPlugin, CanLaunchSuccessTrue) { std::unique_ptr system = std::make_unique(); - std::unique_ptr result = - std::make_unique(); // Return success values from the registery commands. HKEY fake_key = reinterpret_cast(1); @@ -73,20 +55,16 @@ TEST(UrlLauncherPlugin, CanLaunchSuccessTrue) { .WillOnce(DoAll(SetArgPointee<4>(fake_key), Return(ERROR_SUCCESS))); EXPECT_CALL(*system, RegQueryValueExW).WillOnce(Return(ERROR_SUCCESS)); EXPECT_CALL(*system, RegCloseKey(fake_key)).WillOnce(Return(ERROR_SUCCESS)); - // Expect a success response. - EXPECT_CALL(*result, SuccessInternal(Pointee(EncodableValue(true)))); UrlLauncherPlugin plugin(std::move(system)); - plugin.HandleMethodCall( - flutter::MethodCall("canLaunch", - CreateArgumentsWithUrl("https://some.url.com")), - std::move(result)); + ErrorOr result = plugin.CanLaunchUrl("https://some.url.com"); + + ASSERT_FALSE(result.has_error()); + EXPECT_TRUE(result.value()); } TEST(UrlLauncherPlugin, CanLaunchQueryFailure) { std::unique_ptr system = std::make_unique(); - std::unique_ptr result = - std::make_unique(); // Return success values from the registery commands, except for the query, // to simulate a scheme that is in the registry, but has no URL handler. @@ -95,68 +73,52 @@ TEST(UrlLauncherPlugin, CanLaunchQueryFailure) { .WillOnce(DoAll(SetArgPointee<4>(fake_key), Return(ERROR_SUCCESS))); EXPECT_CALL(*system, RegQueryValueExW).WillOnce(Return(ERROR_FILE_NOT_FOUND)); EXPECT_CALL(*system, RegCloseKey(fake_key)).WillOnce(Return(ERROR_SUCCESS)); - // Expect a success response. - EXPECT_CALL(*result, SuccessInternal(Pointee(EncodableValue(false)))); UrlLauncherPlugin plugin(std::move(system)); - plugin.HandleMethodCall( - flutter::MethodCall("canLaunch", - CreateArgumentsWithUrl("https://some.url.com")), - std::move(result)); + ErrorOr result = plugin.CanLaunchUrl("https://some.url.com"); + + ASSERT_FALSE(result.has_error()); + EXPECT_FALSE(result.value()); } TEST(UrlLauncherPlugin, CanLaunchHandlesOpenFailure) { std::unique_ptr system = std::make_unique(); - std::unique_ptr result = - std::make_unique(); // Return failure for opening. EXPECT_CALL(*system, RegOpenKeyExW).WillOnce(Return(ERROR_BAD_PATHNAME)); - // Expect a success response. - EXPECT_CALL(*result, SuccessInternal(Pointee(EncodableValue(false)))); UrlLauncherPlugin plugin(std::move(system)); - plugin.HandleMethodCall( - flutter::MethodCall("canLaunch", - CreateArgumentsWithUrl("https://some.url.com")), - std::move(result)); + ErrorOr result = plugin.CanLaunchUrl("https://some.url.com"); + + ASSERT_FALSE(result.has_error()); + EXPECT_FALSE(result.value()); } TEST(UrlLauncherPlugin, LaunchSuccess) { std::unique_ptr system = std::make_unique(); - std::unique_ptr result = - std::make_unique(); // Return a success value (>32) from launching. EXPECT_CALL(*system, ShellExecuteW) .WillOnce(Return(reinterpret_cast(33))); - // Expect a success response. - EXPECT_CALL(*result, SuccessInternal(Pointee(EncodableValue(true)))); UrlLauncherPlugin plugin(std::move(system)); - plugin.HandleMethodCall( - flutter::MethodCall("launch", - CreateArgumentsWithUrl("https://some.url.com")), - std::move(result)); + std::optional error = plugin.LaunchUrl("https://some.url.com"); + + EXPECT_FALSE(error.has_value()); } TEST(UrlLauncherPlugin, LaunchReportsFailure) { std::unique_ptr system = std::make_unique(); - std::unique_ptr result = - std::make_unique(); // Return a faile value (<=32) from launching. EXPECT_CALL(*system, ShellExecuteW) .WillOnce(Return(reinterpret_cast(32))); - // Expect an error response. - EXPECT_CALL(*result, ErrorInternal); UrlLauncherPlugin plugin(std::move(system)); - plugin.HandleMethodCall( - flutter::MethodCall("launch", - CreateArgumentsWithUrl("https://some.url.com")), - std::move(result)); + std::optional error = plugin.LaunchUrl("https://some.url.com"); + + EXPECT_TRUE(error.has_value()); } } // namespace test -} // namespace url_launcher_plugin +} // namespace url_launcher_windows diff --git a/packages/url_launcher/url_launcher_windows/windows/url_launcher_plugin.cpp b/packages/url_launcher/url_launcher_windows/windows/url_launcher_plugin.cpp index d5f201219c75..1dfee16c4445 100644 --- a/packages/url_launcher/url_launcher_windows/windows/url_launcher_plugin.cpp +++ b/packages/url_launcher/url_launcher_windows/windows/url_launcher_plugin.cpp @@ -13,7 +13,9 @@ #include #include -namespace url_launcher_plugin { +#include "messages.g.h" + +namespace url_launcher_windows { namespace { @@ -62,18 +64,9 @@ std::string GetUrlArgument(const flutter::MethodCall<>& method_call) { // static void UrlLauncherPlugin::RegisterWithRegistrar( flutter::PluginRegistrar* registrar) { - auto channel = std::make_unique>( - registrar->messenger(), "plugins.flutter.io/url_launcher_windows", - &flutter::StandardMethodCodec::GetInstance()); - std::unique_ptr plugin = std::make_unique(); - - channel->SetMethodCallHandler( - [plugin_pointer = plugin.get()](const auto& call, auto result) { - plugin_pointer->HandleMethodCall(call, std::move(result)); - }); - + UrlLauncherApi::SetUp(registrar->messenger(), plugin.get()); registrar->AddPlugin(std::move(plugin)); } @@ -85,37 +78,7 @@ UrlLauncherPlugin::UrlLauncherPlugin(std::unique_ptr system_apis) UrlLauncherPlugin::~UrlLauncherPlugin() = default; -void UrlLauncherPlugin::HandleMethodCall( - const flutter::MethodCall<>& method_call, - std::unique_ptr> result) { - if (method_call.method_name().compare("launch") == 0) { - std::string url = GetUrlArgument(method_call); - if (url.empty()) { - result->Error("argument_error", "No URL provided"); - return; - } - - std::optional error = LaunchUrl(url); - if (error) { - result->Error("open_error", error.value()); - return; - } - result->Success(EncodableValue(true)); - } else if (method_call.method_name().compare("canLaunch") == 0) { - std::string url = GetUrlArgument(method_call); - if (url.empty()) { - result->Error("argument_error", "No URL provided"); - return; - } - - bool can_launch = CanLaunchUrl(url); - result->Success(EncodableValue(can_launch)); - } else { - result->NotImplemented(); - } -} - -bool UrlLauncherPlugin::CanLaunchUrl(const std::string& url) { +ErrorOr UrlLauncherPlugin::CanLaunchUrl(const std::string& url) { size_t separator_location = url.find(":"); if (separator_location == std::string::npos) { return false; @@ -134,7 +97,7 @@ bool UrlLauncherPlugin::CanLaunchUrl(const std::string& url) { return has_handler; } -std::optional UrlLauncherPlugin::LaunchUrl( +std::optional UrlLauncherPlugin::LaunchUrl( const std::string& url) { std::wstring url_wide = Utf16FromUtf8(url); @@ -147,9 +110,9 @@ std::optional UrlLauncherPlugin::LaunchUrl( std::ostringstream error_message; error_message << "Failed to open " << url << ": ShellExecute error code " << status; - return std::optional(error_message.str()); + return FlutterError("open_error", error_message.str()); } return std::nullopt; } -} // namespace url_launcher_plugin +} // namespace url_launcher_windows diff --git a/packages/url_launcher/url_launcher_windows/windows/url_launcher_plugin.h b/packages/url_launcher/url_launcher_windows/windows/url_launcher_plugin.h index 45e70e5fc067..e51cde67ab79 100644 --- a/packages/url_launcher/url_launcher_windows/windows/url_launcher_plugin.h +++ b/packages/url_launcher/url_launcher_windows/windows/url_launcher_plugin.h @@ -10,11 +10,12 @@ #include #include +#include "messages.g.h" #include "system_apis.h" -namespace url_launcher_plugin { +namespace url_launcher_windows { -class UrlLauncherPlugin : public flutter::Plugin { +class UrlLauncherPlugin : public flutter::Plugin, public UrlLauncherApi { public: static void RegisterWithRegistrar(flutter::PluginRegistrar* registrar); @@ -31,18 +32,12 @@ class UrlLauncherPlugin : public flutter::Plugin { UrlLauncherPlugin(const UrlLauncherPlugin&) = delete; UrlLauncherPlugin& operator=(const UrlLauncherPlugin&) = delete; - // Called when a method is called on the plugin channel. - void HandleMethodCall(const flutter::MethodCall<>& method_call, - std::unique_ptr> result); + // UrlLauncherApi: + ErrorOr CanLaunchUrl(const std::string& url) override; + std::optional LaunchUrl(const std::string& url) override; private: - // Returns whether or not the given URL has a registered handler. - bool CanLaunchUrl(const std::string& url); - - // Attempts to launch the given URL. On failure, returns an error string. - std::optional LaunchUrl(const std::string& url); - std::unique_ptr system_apis_; }; -} // namespace url_launcher_plugin +} // namespace url_launcher_windows diff --git a/packages/url_launcher/url_launcher_windows/windows/url_launcher_windows.cpp b/packages/url_launcher/url_launcher_windows/windows/url_launcher_windows.cpp index 05de586d8fe0..726709386fa6 100644 --- a/packages/url_launcher/url_launcher_windows/windows/url_launcher_windows.cpp +++ b/packages/url_launcher/url_launcher_windows/windows/url_launcher_windows.cpp @@ -9,7 +9,7 @@ void UrlLauncherWindowsRegisterWithRegistrar( FlutterDesktopPluginRegistrarRef registrar) { - url_launcher_plugin::UrlLauncherPlugin::RegisterWithRegistrar( + url_launcher_windows::UrlLauncherPlugin::RegisterWithRegistrar( flutter::PluginRegistrarManager::GetInstance() ->GetRegistrar(registrar)); } From dc8ad770163d45f35eede3cac89f55ba432c19c7 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Wed, 25 Jan 2023 13:32:19 -0500 Subject: [PATCH 037/130] Roll Flutter from c35efdaa6854 to a815ee634202 (22 revisions) (#7025) * 373523184 Cleanup old Dart SDK layout compatibility (flutter/flutter#118819) * 4d250302a Add leak_tracker as dev_dependency. (flutter/flutter#118952) * e3c51a2f2 Add Windows unit tests to plugin template (flutter/flutter#118638) * d20dd9e4b Roll Flutter Engine from 7d3233d26d09 to 71ee5f19bc16 (15 revisions) (flutter/flutter#119081) * 5dabe102a Fix path name to discover debug apk on add2app builds (flutter/flutter#117999) * 50ed8a34b Enable `unnecessary_null_comparison` check (flutter/flutter#118849) * 455e6aca5 Test integration test apps' runner files against current template app (flutter/flutter#118646) * a788e1b31 Roll Flutter Engine from 71ee5f19bc16 to 59ea78bfabda (2 revisions) (flutter/flutter#119087) * c35370cf0 Roll Flutter Engine from 59ea78bfabda to 2499a5d9fca7 (2 revisions) (flutter/flutter#119089) * 2f0dd5673 Refactor highlight handling in FocusManager (flutter/flutter#119075) * 2759f3f0b Roll Flutter Engine from 2499a5d9fca7 to d98926c32ee7 (2 revisions) (flutter/flutter#119090) * 760fb2115 Roll Flutter Engine from d98926c32ee7 to bec40654a5d7 (2 revisions) (flutter/flutter#119093) * bbca694ef Roll Flutter Engine from bec40654a5d7 to 5405f2c26e85 (2 revisions) (flutter/flutter#119095) * 6414c3604 f1464b49c Manually roll ANGLE, vulkan-deps, SwiftShader (flutter/engine#38650) (flutter/flutter#119097) * 426cdd90c 55bb8deaf [Impeller] Linear sample atlas glyphs when the CTM isn't translation/scale only (flutter/engine#39112) (flutter/flutter#119098) * 83c3a61e3 Only emit image painting events in debug & profile modes. (flutter/flutter#118872) * b113df2dc bffb98352 Roll Skia from b72fececbdcc to 8ffd5c20d634 (3 revisions) (flutter/engine#39114) (flutter/flutter#119099) * 351466aea Add Decoding Flutter videos to API docs (flutter/flutter#116454) * 318f8758b Pass through magnifierConfiguration (flutter/flutter#118270) * eced23eab d39ab638b Roll Fuchsia Mac SDK from MUvFS0baOnigVUIND... to _H53AyDxR9Pm2TbwN... (flutter/engine#39122) (flutter/flutter#119126) * 29ab437e2 Add Material 3 `CheckboxListTile` example and update existing examples (flutter/flutter#118792) * a815ee634 8efc7183b Roll Skia from 8ffd5c20d634 to da5034f9d117 (4 revisions) (flutter/engine#39123) (flutter/flutter#119129) --- .ci/flutter_master.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/flutter_master.version b/.ci/flutter_master.version index bd0f85100b71..1555264bb14a 100644 --- a/.ci/flutter_master.version +++ b/.ci/flutter_master.version @@ -1 +1 @@ -c35efdaa6854330b28d3968f4ec8c073e5c49252 +a815ee634202c0a9e1be9c73450226c7cff97cff From e9406bc209a2a7fce4b8bd627218f339435acf31 Mon Sep 17 00:00:00 2001 From: Camille Simon <43054281+camsim99@users.noreply.github.com> Date: Wed, 25 Jan 2023 14:20:16 -0800 Subject: [PATCH 038/130] [camerax] Adds functionality to bind UseCases to a lifecycle (#6939) * Copy over code from proof of concept * Add dart tests * Fix dart tests * Add java tests * Add me as owner and changelog change * Fix analyzer * Add instance manager fix * Update comment * Undo instance manager changes * Formatting * Fix analyze * Address review * Fix analyze * Add import * Fix assertion error * Remove unecessary this keywrod * Update packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ProcessCameraProviderHostApiImpl.java Co-authored-by: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> Co-authored-by: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> --- CODEOWNERS | 1 + .../camera_android_camerax/CHANGELOG.md | 1 + .../camerax/CameraAndroidCameraxPlugin.java | 18 +-- .../plugins/camerax/CameraFlutterApiImpl.java | 22 +++ .../camerax/CameraInfoHostApiImpl.java | 4 +- .../camerax/CameraSelectorHostApiImpl.java | 7 +- .../camerax/GeneratedCameraXLibrary.java | 145 ++++++++++++++++++ .../ProcessCameraProviderHostApiImpl.java | 81 ++++++++-- .../flutter/plugins/camerax/CameraTest.java | 52 +++++++ .../camerax/ProcessCameraProviderTest.java | 56 +++++++ ...roid_camera_camerax_flutter_api_impls.dart | 7 + .../lib/src/camera.dart | 53 +++++++ .../lib/src/camerax_library.pigeon.dart | 114 ++++++++++++++ .../lib/src/process_camera_provider.dart | 85 +++++++++- .../lib/src/use_case.dart | 14 ++ .../pigeons/camerax_library.dart | 12 ++ .../test/camera_info_test.mocks.dart | 10 +- .../test/camera_selector_test.mocks.dart | 36 ++++- .../test/camera_test.dart | 26 ++++ .../test/process_camera_provider_test.dart | 111 ++++++++++++++ .../process_camera_provider_test.mocks.dart | 60 +++++++- .../test/test_camerax_library.pigeon.dart | 74 +++++++++ 22 files changed, 945 insertions(+), 44 deletions(-) create mode 100644 packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraFlutterApiImpl.java create mode 100644 packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraTest.java create mode 100644 packages/camera/camera_android_camerax/lib/src/camera.dart create mode 100644 packages/camera/camera_android_camerax/lib/src/use_case.dart create mode 100644 packages/camera/camera_android_camerax/test/camera_test.dart diff --git a/CODEOWNERS b/CODEOWNERS index f128098711f9..8a46d52a07d9 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -28,6 +28,7 @@ packages/**/*_web/** @ditman # - Android packages/camera/camera_android/** @camsim99 +packages/camera/camera_android_camerax/** @camsim99 packages/espresso/** @GaryQian packages/flutter_plugin_android_lifecycle/** @GaryQian packages/google_maps_flutter/google_maps_flutter_android/** @GaryQian diff --git a/packages/camera/camera_android_camerax/CHANGELOG.md b/packages/camera/camera_android_camerax/CHANGELOG.md index f94a86044ad8..389fc31e26e7 100644 --- a/packages/camera/camera_android_camerax/CHANGELOG.md +++ b/packages/camera/camera_android_camerax/CHANGELOG.md @@ -5,3 +5,4 @@ * Adds CameraSelector class. * Adds ProcessCameraProvider class. * Bump CameraX version to 1.3.0-alpha02. +* Adds Camera and UseCase classes, along with methods for binding UseCases to a lifecycle with the ProcessCameraProvider. diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraAndroidCameraxPlugin.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraAndroidCameraxPlugin.java index b8fbaf539c32..7ee7263f7779 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraAndroidCameraxPlugin.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraAndroidCameraxPlugin.java @@ -6,6 +6,7 @@ import android.content.Context; import androidx.annotation.NonNull; +import androidx.lifecycle.LifecycleOwner; import io.flutter.embedding.engine.plugins.FlutterPlugin; import io.flutter.embedding.engine.plugins.activity.ActivityAware; import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; @@ -15,7 +16,7 @@ public final class CameraAndroidCameraxPlugin implements FlutterPlugin, ActivityAware { private InstanceManager instanceManager; private FlutterPluginBinding pluginBinding; - private ProcessCameraProviderHostApiImpl processCameraProviderHostApi; + public ProcessCameraProviderHostApiImpl processCameraProviderHostApi; /** * Initialize this within the {@code #configureFlutterEngine} of a Flutter activity or fragment. @@ -36,10 +37,10 @@ void setUp(BinaryMessenger binaryMessenger, Context context) { // Set up Host APIs. GeneratedCameraXLibrary.CameraInfoHostApi.setup( binaryMessenger, new CameraInfoHostApiImpl(instanceManager)); - GeneratedCameraXLibrary.JavaObjectHostApi.setup( - binaryMessenger, new JavaObjectHostApiImpl(instanceManager)); GeneratedCameraXLibrary.CameraSelectorHostApi.setup( binaryMessenger, new CameraSelectorHostApiImpl(binaryMessenger, instanceManager)); + GeneratedCameraXLibrary.JavaObjectHostApi.setup( + binaryMessenger, new JavaObjectHostApiImpl(instanceManager)); processCameraProviderHostApi = new ProcessCameraProviderHostApiImpl(binaryMessenger, instanceManager, context); GeneratedCameraXLibrary.ProcessCameraProviderHostApi.setup( @@ -49,10 +50,6 @@ void setUp(BinaryMessenger binaryMessenger, Context context) { @Override public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) { pluginBinding = flutterPluginBinding; - (new CameraAndroidCameraxPlugin()) - .setUp( - flutterPluginBinding.getBinaryMessenger(), - flutterPluginBinding.getApplicationContext()); } @Override @@ -66,7 +63,10 @@ public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { @Override public void onAttachedToActivity(@NonNull ActivityPluginBinding activityPluginBinding) { - updateContext(activityPluginBinding.getActivity()); + setUp(pluginBinding.getBinaryMessenger(), pluginBinding.getApplicationContext()); + updateContext(pluginBinding.getApplicationContext()); + processCameraProviderHostApi.setLifecycleOwner( + (LifecycleOwner) activityPluginBinding.getActivity()); } @Override @@ -89,7 +89,7 @@ public void onDetachedFromActivity() { * Updates context that is used to fetch the corresponding instance of a {@code * ProcessCameraProvider}. */ - private void updateContext(Context context) { + public void updateContext(Context context) { if (processCameraProviderHostApi != null) { processCameraProviderHostApi.setContext(context); } diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraFlutterApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraFlutterApiImpl.java new file mode 100644 index 000000000000..a03548399485 --- /dev/null +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraFlutterApiImpl.java @@ -0,0 +1,22 @@ +// Copyright 2013 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.camerax; + +import androidx.camera.core.Camera; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugins.camerax.GeneratedCameraXLibrary.CameraFlutterApi; + +public class CameraFlutterApiImpl extends CameraFlutterApi { + private final InstanceManager instanceManager; + + public CameraFlutterApiImpl(BinaryMessenger binaryMessenger, InstanceManager instanceManager) { + super(binaryMessenger); + this.instanceManager = instanceManager; + } + + void create(Camera camera, Reply reply) { + create(instanceManager.addHostCreatedInstance(camera), reply); + } +} diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraInfoHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraInfoHostApiImpl.java index 7daba0d38d6a..d960b7fff70a 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraInfoHostApiImpl.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraInfoHostApiImpl.java @@ -7,6 +7,7 @@ import androidx.annotation.NonNull; import androidx.camera.core.CameraInfo; import io.flutter.plugins.camerax.GeneratedCameraXLibrary.CameraInfoHostApi; +import java.util.Objects; public class CameraInfoHostApiImpl implements CameraInfoHostApi { private final InstanceManager instanceManager; @@ -17,7 +18,8 @@ public CameraInfoHostApiImpl(InstanceManager instanceManager) { @Override public Long getSensorRotationDegrees(@NonNull Long identifier) { - CameraInfo cameraInfo = (CameraInfo) instanceManager.getInstance(identifier); + CameraInfo cameraInfo = + (CameraInfo) Objects.requireNonNull(instanceManager.getInstance(identifier)); return Long.valueOf(cameraInfo.getSensorRotationDegrees()); } } diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraSelectorHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraSelectorHostApiImpl.java index 9c559a72e63c..87c69dea9e1c 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraSelectorHostApiImpl.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraSelectorHostApiImpl.java @@ -12,6 +12,7 @@ import io.flutter.plugins.camerax.GeneratedCameraXLibrary.CameraSelectorHostApi; import java.util.ArrayList; import java.util.List; +import java.util.Objects; public class CameraSelectorHostApiImpl implements CameraSelectorHostApi { private final BinaryMessenger binaryMessenger; @@ -41,13 +42,15 @@ public void create(@NonNull Long identifier, Long lensFacing) { @Override public List filter(@NonNull Long identifier, @NonNull List cameraInfoIds) { - CameraSelector cameraSelector = (CameraSelector) instanceManager.getInstance(identifier); + CameraSelector cameraSelector = + (CameraSelector) Objects.requireNonNull(instanceManager.getInstance(identifier)); List cameraInfosForFilter = new ArrayList(); for (Number cameraInfoAsNumber : cameraInfoIds) { Long cameraInfoId = cameraInfoAsNumber.longValue(); - CameraInfo cameraInfo = (CameraInfo) instanceManager.getInstance(cameraInfoId); + CameraInfo cameraInfo = + (CameraInfo) Objects.requireNonNull(instanceManager.getInstance(cameraInfoId)); cameraInfosForFilter.add(cameraInfo); } diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/GeneratedCameraXLibrary.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/GeneratedCameraXLibrary.java index 041564c3bfcb..8c42a7911768 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/GeneratedCameraXLibrary.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/GeneratedCameraXLibrary.java @@ -332,6 +332,16 @@ public interface ProcessCameraProviderHostApi { @NonNull List getAvailableCameraInfos(@NonNull Long identifier); + @NonNull + Long bindToLifecycle( + @NonNull Long identifier, + @NonNull Long cameraSelectorIdentifier, + @NonNull List useCaseIds); + + void unbind(@NonNull Long identifier, @NonNull List useCaseIds); + + void unbindAll(@NonNull Long identifier); + /** The codec used by ProcessCameraProviderHostApi. */ static MessageCodec getCodec() { return ProcessCameraProviderHostApiCodec.INSTANCE; @@ -405,6 +415,107 @@ public void error(Throwable error) { channel.setMessageHandler(null); } } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.ProcessCameraProviderHostApi.bindToLifecycle", + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + Map wrapped = new HashMap<>(); + try { + ArrayList args = (ArrayList) message; + Number identifierArg = (Number) args.get(0); + if (identifierArg == null) { + throw new NullPointerException("identifierArg unexpectedly null."); + } + Number cameraSelectorIdentifierArg = (Number) args.get(1); + if (cameraSelectorIdentifierArg == null) { + throw new NullPointerException( + "cameraSelectorIdentifierArg unexpectedly null."); + } + List useCaseIdsArg = (List) args.get(2); + if (useCaseIdsArg == null) { + throw new NullPointerException("useCaseIdsArg unexpectedly null."); + } + Long output = + api.bindToLifecycle( + (identifierArg == null) ? null : identifierArg.longValue(), + (cameraSelectorIdentifierArg == null) + ? null + : cameraSelectorIdentifierArg.longValue(), + useCaseIdsArg); + wrapped.put("result", output); + } catch (Error | RuntimeException exception) { + wrapped.put("error", wrapError(exception)); + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.ProcessCameraProviderHostApi.unbind", + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + Map wrapped = new HashMap<>(); + try { + ArrayList args = (ArrayList) message; + Number identifierArg = (Number) args.get(0); + if (identifierArg == null) { + throw new NullPointerException("identifierArg unexpectedly null."); + } + List useCaseIdsArg = (List) args.get(1); + if (useCaseIdsArg == null) { + throw new NullPointerException("useCaseIdsArg unexpectedly null."); + } + api.unbind( + (identifierArg == null) ? null : identifierArg.longValue(), useCaseIdsArg); + wrapped.put("result", null); + } catch (Error | RuntimeException exception) { + wrapped.put("error", wrapError(exception)); + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.ProcessCameraProviderHostApi.unbindAll", + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + Map wrapped = new HashMap<>(); + try { + ArrayList args = (ArrayList) message; + Number identifierArg = (Number) args.get(0); + if (identifierArg == null) { + throw new NullPointerException("identifierArg unexpectedly null."); + } + api.unbindAll((identifierArg == null) ? null : identifierArg.longValue()); + wrapped.put("result", null); + } catch (Error | RuntimeException exception) { + wrapped.put("error", wrapError(exception)); + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } } } @@ -445,6 +556,40 @@ public void create(@NonNull Long identifierArg, Reply callback) { } } + private static class CameraFlutterApiCodec extends StandardMessageCodec { + public static final CameraFlutterApiCodec INSTANCE = new CameraFlutterApiCodec(); + + private CameraFlutterApiCodec() {} + } + + /** Generated class from Pigeon that represents Flutter messages that can be called from Java. */ + public static class CameraFlutterApi { + private final BinaryMessenger binaryMessenger; + + public CameraFlutterApi(BinaryMessenger argBinaryMessenger) { + this.binaryMessenger = argBinaryMessenger; + } + + public interface Reply { + void reply(T reply); + } + + static MessageCodec getCodec() { + return CameraFlutterApiCodec.INSTANCE; + } + + public void create(@NonNull Long identifierArg, Reply callback) { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, "dev.flutter.pigeon.CameraFlutterApi.create", getCodec()); + channel.send( + new ArrayList(Arrays.asList(identifierArg)), + channelReply -> { + callback.reply(null); + }); + } + } + private static Map wrapError(Throwable exception) { Map errorMap = new HashMap<>(); errorMap.put("message", exception.toString()); diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ProcessCameraProviderHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ProcessCameraProviderHostApiImpl.java index 19c5eb5b3f70..f82f18f054c9 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ProcessCameraProviderHostApiImpl.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ProcessCameraProviderHostApiImpl.java @@ -6,20 +6,26 @@ import android.content.Context; import androidx.annotation.NonNull; +import androidx.camera.core.Camera; import androidx.camera.core.CameraInfo; +import androidx.camera.core.CameraSelector; +import androidx.camera.core.UseCase; import androidx.camera.lifecycle.ProcessCameraProvider; import androidx.core.content.ContextCompat; +import androidx.lifecycle.LifecycleOwner; import com.google.common.util.concurrent.ListenableFuture; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugins.camerax.GeneratedCameraXLibrary.ProcessCameraProviderHostApi; import java.util.ArrayList; import java.util.List; +import java.util.Objects; public class ProcessCameraProviderHostApiImpl implements ProcessCameraProviderHostApi { private final BinaryMessenger binaryMessenger; private final InstanceManager instanceManager; private Context context; + private LifecycleOwner lifecycleOwner; public ProcessCameraProviderHostApiImpl( BinaryMessenger binaryMessenger, InstanceManager instanceManager, Context context) { @@ -28,6 +34,10 @@ public ProcessCameraProviderHostApiImpl( this.context = context; } + public void setLifecycleOwner(LifecycleOwner lifecycleOwner) { + this.lifecycleOwner = lifecycleOwner; + } + /** * Sets the context that the {@code ProcessCameraProvider} will use to attach the lifecycle of the * camera to. @@ -40,8 +50,8 @@ public void setContext(Context context) { } /** - * Returns the instance of the ProcessCameraProvider to manage the lifecycle of the camera for the - * current {@code Context}. + * Returns the instance of the {@code ProcessCameraProvider} to manage the lifecycle of the camera + * for the current {@code Context}. */ @Override public void getInstance(GeneratedCameraXLibrary.Result result) { @@ -54,11 +64,9 @@ public void getInstance(GeneratedCameraXLibrary.Result result) { // Camera provider is now guaranteed to be available. ProcessCameraProvider processCameraProvider = processCameraProviderFuture.get(); - if (!instanceManager.containsInstance(processCameraProvider)) { - final ProcessCameraProviderFlutterApiImpl flutterApi = - new ProcessCameraProviderFlutterApiImpl(binaryMessenger, instanceManager); - flutterApi.create(processCameraProvider, reply -> {}); - } + final ProcessCameraProviderFlutterApiImpl flutterApi = + new ProcessCameraProviderFlutterApiImpl(binaryMessenger, instanceManager); + flutterApi.create(processCameraProvider, reply -> {}); result.success(instanceManager.getIdentifierForStrongReference(processCameraProvider)); } catch (Exception e) { result.error(e); @@ -67,11 +75,11 @@ public void getInstance(GeneratedCameraXLibrary.Result result) { ContextCompat.getMainExecutor(context)); } - /** Returns cameras available to the ProcessCameraProvider. */ + /** Returns cameras available to the {@code ProcessCameraProvider}. */ @Override public List getAvailableCameraInfos(@NonNull Long identifier) { ProcessCameraProvider processCameraProvider = - (ProcessCameraProvider) instanceManager.getInstance(identifier); + (ProcessCameraProvider) Objects.requireNonNull(instanceManager.getInstance(identifier)); List availableCameras = processCameraProvider.getAvailableCameraInfos(); List availableCamerasIds = new ArrayList(); @@ -84,4 +92,59 @@ public List getAvailableCameraInfos(@NonNull Long identifier) { } return availableCamerasIds; } + + /** + * Binds specified {@code UseCase}s to the lifecycle of the {@code LifecycleOwner} that + * corresponds to this instance and returns the instance of the {@code Camera} whose lifecycle + * that {@code LifecycleOwner} reflects. + */ + @Override + public Long bindToLifecycle( + @NonNull Long identifier, + @NonNull Long cameraSelectorIdentifier, + @NonNull List useCaseIds) { + ProcessCameraProvider processCameraProvider = + (ProcessCameraProvider) Objects.requireNonNull(instanceManager.getInstance(identifier)); + CameraSelector cameraSelector = + (CameraSelector) + Objects.requireNonNull(instanceManager.getInstance(cameraSelectorIdentifier)); + UseCase[] useCases = new UseCase[useCaseIds.size()]; + for (int i = 0; i < useCaseIds.size(); i++) { + useCases[i] = + (UseCase) + Objects.requireNonNull( + instanceManager.getInstance(((Number) useCaseIds.get(i)).longValue())); + } + + Camera camera = + processCameraProvider.bindToLifecycle( + (LifecycleOwner) lifecycleOwner, cameraSelector, useCases); + + final CameraFlutterApiImpl cameraFlutterApi = + new CameraFlutterApiImpl(binaryMessenger, instanceManager); + cameraFlutterApi.create(camera, result -> {}); + + return instanceManager.getIdentifierForStrongReference(camera); + } + + @Override + public void unbind(@NonNull Long identifier, @NonNull List useCaseIds) { + ProcessCameraProvider processCameraProvider = + (ProcessCameraProvider) Objects.requireNonNull(instanceManager.getInstance(identifier)); + UseCase[] useCases = new UseCase[useCaseIds.size()]; + for (int i = 0; i < useCaseIds.size(); i++) { + useCases[i] = + (UseCase) + Objects.requireNonNull( + instanceManager.getInstance(((Number) useCaseIds.get(i)).longValue())); + } + processCameraProvider.unbind(useCases); + } + + @Override + public void unbindAll(@NonNull Long identifier) { + ProcessCameraProvider processCameraProvider = + (ProcessCameraProvider) Objects.requireNonNull(instanceManager.getInstance(identifier)); + processCameraProvider.unbindAll(); + } } diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraTest.java new file mode 100644 index 000000000000..e2135b3945b0 --- /dev/null +++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraTest.java @@ -0,0 +1,52 @@ +// Copyright 2013 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.camerax; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import androidx.camera.core.Camera; +import io.flutter.plugin.common.BinaryMessenger; +import java.util.Objects; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +public class CameraTest { + @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); + + @Mock public BinaryMessenger mockBinaryMessenger; + @Mock public Camera camera; + + InstanceManager testInstanceManager; + + @Before + public void setUp() { + testInstanceManager = InstanceManager.open(identifier -> {}); + } + + @After + public void tearDown() { + testInstanceManager.close(); + } + + @Test + public void flutterApiCreateTest() { + final CameraFlutterApiImpl spyFlutterApi = + spy(new CameraFlutterApiImpl(mockBinaryMessenger, testInstanceManager)); + + spyFlutterApi.create(camera, reply -> {}); + + final long identifier = + Objects.requireNonNull(testInstanceManager.getIdentifierForStrongReference(camera)); + verify(spyFlutterApi).create(eq(identifier), any()); + } +} diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ProcessCameraProviderTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ProcessCameraProviderTest.java index 5008e4ef34b0..47b4ed6ad26d 100644 --- a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ProcessCameraProviderTest.java +++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ProcessCameraProviderTest.java @@ -13,8 +13,12 @@ import static org.mockito.Mockito.when; import android.content.Context; +import androidx.camera.core.Camera; import androidx.camera.core.CameraInfo; +import androidx.camera.core.CameraSelector; +import androidx.camera.core.UseCase; import androidx.camera.lifecycle.ProcessCameraProvider; +import androidx.lifecycle.LifecycleOwner; import androidx.test.core.app.ApplicationProvider; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; @@ -99,6 +103,58 @@ public void getAvailableCameraInfosTest() { verify(processCameraProvider).getAvailableCameraInfos(); } + @Test + public void bindToLifecycleTest() { + final ProcessCameraProviderHostApiImpl processCameraProviderHostApi = + new ProcessCameraProviderHostApiImpl(mockBinaryMessenger, testInstanceManager, context); + final Camera mockCamera = mock(Camera.class); + final CameraSelector mockCameraSelector = mock(CameraSelector.class); + final UseCase mockUseCase = mock(UseCase.class); + UseCase[] mockUseCases = new UseCase[] {mockUseCase}; + + LifecycleOwner mockLifecycleOwner = mock(LifecycleOwner.class); + processCameraProviderHostApi.setLifecycleOwner(mockLifecycleOwner); + + testInstanceManager.addDartCreatedInstance(processCameraProvider, 0); + testInstanceManager.addDartCreatedInstance(mockCameraSelector, 1); + testInstanceManager.addDartCreatedInstance(mockUseCase, 2); + testInstanceManager.addDartCreatedInstance(mockCamera, 3); + + when(processCameraProvider.bindToLifecycle( + mockLifecycleOwner, mockCameraSelector, mockUseCases)) + .thenReturn(mockCamera); + + assertEquals( + processCameraProviderHostApi.bindToLifecycle(0L, 1L, Arrays.asList(2L)), Long.valueOf(3)); + verify(processCameraProvider) + .bindToLifecycle(mockLifecycleOwner, mockCameraSelector, mockUseCases); + } + + @Test + public void unbindTest() { + final ProcessCameraProviderHostApiImpl processCameraProviderHostApi = + new ProcessCameraProviderHostApiImpl(mockBinaryMessenger, testInstanceManager, context); + final UseCase mockUseCase = mock(UseCase.class); + UseCase[] mockUseCases = new UseCase[] {mockUseCase}; + + testInstanceManager.addDartCreatedInstance(processCameraProvider, 0); + testInstanceManager.addDartCreatedInstance(mockUseCase, 1); + + processCameraProviderHostApi.unbind(0L, Arrays.asList(1L)); + verify(processCameraProvider).unbind(mockUseCases); + } + + @Test + public void unbindAllTest() { + final ProcessCameraProviderHostApiImpl processCameraProviderHostApi = + new ProcessCameraProviderHostApiImpl(mockBinaryMessenger, testInstanceManager, context); + + testInstanceManager.addDartCreatedInstance(processCameraProvider, 0); + + processCameraProviderHostApi.unbindAll(0L); + verify(processCameraProvider).unbindAll(); + } + @Test public void flutterApiCreateTest() { final ProcessCameraProviderFlutterApiImpl spyFlutterApi = diff --git a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax_flutter_api_impls.dart b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax_flutter_api_impls.dart index 9c6564a06c08..620831bfe54d 100644 --- a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax_flutter_api_impls.dart +++ b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax_flutter_api_impls.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'camera.dart'; import 'camera_info.dart'; import 'camera_selector.dart'; import 'camerax_library.pigeon.dart'; @@ -13,6 +14,7 @@ class AndroidCameraXCameraFlutterApis { /// Creates a [AndroidCameraXCameraFlutterApis]. AndroidCameraXCameraFlutterApis({ JavaObjectFlutterApiImpl? javaObjectFlutterApi, + CameraFlutterApiImpl? cameraFlutterApi, CameraInfoFlutterApiImpl? cameraInfoFlutterApi, CameraSelectorFlutterApiImpl? cameraSelectorFlutterApi, ProcessCameraProviderFlutterApiImpl? processCameraProviderFlutterApi, @@ -25,6 +27,7 @@ class AndroidCameraXCameraFlutterApis { cameraSelectorFlutterApi ?? CameraSelectorFlutterApiImpl(); this.processCameraProviderFlutterApi = processCameraProviderFlutterApi ?? ProcessCameraProviderFlutterApiImpl(); + this.cameraFlutterApi = cameraFlutterApi ?? CameraFlutterApiImpl(); } static bool _haveBeenSetUp = false; @@ -48,6 +51,9 @@ class AndroidCameraXCameraFlutterApis { late final ProcessCameraProviderFlutterApiImpl processCameraProviderFlutterApi; + /// Flutter Api for [Camera]. + late final CameraFlutterApiImpl cameraFlutterApi; + /// Ensures all the Flutter APIs have been setup to receive calls from native code. void ensureSetUp() { if (!_haveBeenSetUp) { @@ -55,6 +61,7 @@ class AndroidCameraXCameraFlutterApis { CameraInfoFlutterApi.setup(cameraInfoFlutterApi); CameraSelectorFlutterApi.setup(cameraSelectorFlutterApi); ProcessCameraProviderFlutterApi.setup(processCameraProviderFlutterApi); + CameraFlutterApi.setup(cameraFlutterApi); _haveBeenSetUp = true; } } diff --git a/packages/camera/camera_android_camerax/lib/src/camera.dart b/packages/camera/camera_android_camerax/lib/src/camera.dart new file mode 100644 index 000000000000..0a3820cd0248 --- /dev/null +++ b/packages/camera/camera_android_camerax/lib/src/camera.dart @@ -0,0 +1,53 @@ +// Copyright 2013 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. + +import 'package:flutter/services.dart' show BinaryMessenger; + +import 'android_camera_camerax_flutter_api_impls.dart'; +import 'camerax_library.pigeon.dart'; +import 'instance_manager.dart'; +import 'java_object.dart'; + +/// The interface used to control the flow of data of use cases, control the +/// camera, and publich the state of the camera. +/// +/// See https://developer.android.com/reference/androidx/camera/core/Camera. +class Camera extends JavaObject { + /// Constructs a [Camera] that is not automatically attached to a native object. + Camera.detached({super.binaryMessenger, super.instanceManager}) + : super.detached() { + AndroidCameraXCameraFlutterApis.instance.ensureSetUp(); + } +} + +/// Flutter API implementation of [Camera]. +class CameraFlutterApiImpl implements CameraFlutterApi { + /// Constructs a [CameraSelectorFlutterApiImpl]. + CameraFlutterApiImpl({ + this.binaryMessenger, + InstanceManager? instanceManager, + }) : instanceManager = instanceManager ?? JavaObject.globalInstanceManager; + + /// Receives binary data across the Flutter platform barrier. + /// + /// If it is null, the default BinaryMessenger will be used which routes to + /// the host platform. + final BinaryMessenger? binaryMessenger; + + /// Maintains instances stored to communicate with native language objects. + final InstanceManager instanceManager; + + @override + void create(int identifier) { + instanceManager.addHostCreatedInstance( + Camera.detached( + binaryMessenger: binaryMessenger, instanceManager: instanceManager), + identifier, + onCopy: (Camera original) { + return Camera.detached( + binaryMessenger: binaryMessenger, instanceManager: instanceManager); + }, + ); + } +} diff --git a/packages/camera/camera_android_camerax/lib/src/camerax_library.pigeon.dart b/packages/camera/camera_android_camerax/lib/src/camerax_library.pigeon.dart index c0b052378def..636a375145b9 100644 --- a/packages/camera/camera_android_camerax/lib/src/camerax_library.pigeon.dart +++ b/packages/camera/camera_android_camerax/lib/src/camerax_library.pigeon.dart @@ -338,6 +338,89 @@ class ProcessCameraProviderHostApi { return (replyMap['result'] as List?)!.cast(); } } + + Future bindToLifecycle(int arg_identifier, + int arg_cameraSelectorIdentifier, List arg_useCaseIds) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.ProcessCameraProviderHostApi.bindToLifecycle', + codec, + binaryMessenger: _binaryMessenger); + final Map? replyMap = await channel.send([ + arg_identifier, + arg_cameraSelectorIdentifier, + arg_useCaseIds + ]) as Map?; + if (replyMap == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyMap['error'] != null) { + final Map error = + (replyMap['error'] as Map?)!; + throw PlatformException( + code: (error['code'] as String?)!, + message: error['message'] as String?, + details: error['details'], + ); + } else if (replyMap['result'] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (replyMap['result'] as int?)!; + } + } + + Future unbind(int arg_identifier, List arg_useCaseIds) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.ProcessCameraProviderHostApi.unbind', codec, + binaryMessenger: _binaryMessenger); + final Map? replyMap = + await channel.send([arg_identifier, arg_useCaseIds]) + as Map?; + if (replyMap == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyMap['error'] != null) { + final Map error = + (replyMap['error'] as Map?)!; + throw PlatformException( + code: (error['code'] as String?)!, + message: error['message'] as String?, + details: error['details'], + ); + } else { + return; + } + } + + Future unbindAll(int arg_identifier) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.ProcessCameraProviderHostApi.unbindAll', codec, + binaryMessenger: _binaryMessenger); + final Map? replyMap = + await channel.send([arg_identifier]) as Map?; + if (replyMap == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyMap['error'] != null) { + final Map error = + (replyMap['error'] as Map?)!; + throw PlatformException( + code: (error['code'] as String?)!, + message: error['message'] as String?, + details: error['details'], + ); + } else { + return; + } + } } class _ProcessCameraProviderFlutterApiCodec extends StandardMessageCodec { @@ -372,3 +455,34 @@ abstract class ProcessCameraProviderFlutterApi { } } } + +class _CameraFlutterApiCodec extends StandardMessageCodec { + const _CameraFlutterApiCodec(); +} + +abstract class CameraFlutterApi { + static const MessageCodec codec = _CameraFlutterApiCodec(); + + void create(int identifier); + static void setup(CameraFlutterApi? api, {BinaryMessenger? binaryMessenger}) { + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.CameraFlutterApi.create', codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMessageHandler(null); + } else { + channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.CameraFlutterApi.create was null.'); + final List args = (message as List?)!; + final int? arg_identifier = (args[0] as int?); + assert(arg_identifier != null, + 'Argument for dev.flutter.pigeon.CameraFlutterApi.create was null, expected non-null int.'); + api.create(arg_identifier!); + return; + }); + } + } + } +} diff --git a/packages/camera/camera_android_camerax/lib/src/process_camera_provider.dart b/packages/camera/camera_android_camerax/lib/src/process_camera_provider.dart index 5a67fa7e4dc3..30c8162654ad 100644 --- a/packages/camera/camera_android_camerax/lib/src/process_camera_provider.dart +++ b/packages/camera/camera_android_camerax/lib/src/process_camera_provider.dart @@ -5,10 +5,13 @@ import 'package:flutter/services.dart'; import 'android_camera_camerax_flutter_api_impls.dart'; +import 'camera.dart'; import 'camera_info.dart'; +import 'camera_selector.dart'; import 'camerax_library.pigeon.dart'; import 'instance_manager.dart'; import 'java_object.dart'; +import 'use_case.dart'; /// Provides an object to manage the camera. /// @@ -42,6 +45,25 @@ class ProcessCameraProvider extends JavaObject { Future> getAvailableCameraInfos() { return _api.getAvailableCameraInfosFromInstances(this); } + + /// Binds the specified [UseCase]s to the lifecycle of the camera that it + /// returns. + Future bindToLifecycle( + CameraSelector cameraSelector, List useCases) { + return _api.bindToLifecycleFromInstances(this, cameraSelector, useCases); + } + + /// Unbinds specified [UseCase]s from the lifecycle of the camera that this + /// instance tracks. + void unbind(List useCases) { + _api.unbindFromInstances(this, useCases); + } + + /// Unbinds all previously bound [UseCase]s from the lifecycle of the camera + /// that this tracks. + void unbindAll() { + _api.unbindAllFromInstances(this); + } } /// Host API implementation of [ProcessCameraProvider]. @@ -69,22 +91,71 @@ class ProcessCameraProviderHostApiImpl extends ProcessCameraProviderHostApi { as ProcessCameraProvider; } + /// Gets identifier that the [instanceManager] has set for + /// the [ProcessCameraProvider] instance. + int getProcessCameraProviderIdentifier(ProcessCameraProvider instance) { + final int? identifier = instanceManager.getIdentifier(instance); + + assert(identifier != null, + 'No ProcessCameraProvider has the identifer of that which was requested.'); + return identifier!; + } + /// Retrives the list of CameraInfos corresponding to the available cameras. Future> getAvailableCameraInfosFromInstances( ProcessCameraProvider instance) async { - int? identifier = instanceManager.getIdentifier(instance); - identifier ??= instanceManager.addDartCreatedInstance(instance, - onCopy: (ProcessCameraProvider original) { - return ProcessCameraProvider.detached( - binaryMessenger: binaryMessenger, instanceManager: instanceManager); - }); - + final int identifier = getProcessCameraProviderIdentifier(instance); final List cameraInfos = await getAvailableCameraInfos(identifier); return cameraInfos .map((int? id) => instanceManager.getInstanceWithWeakReference(id!)! as CameraInfo) .toList(); } + + /// Binds the specified [UseCase]s to the lifecycle of the camera which + /// the provided [ProcessCameraProvider] instance tracks. + /// + /// The instance of the camera whose lifecycle the [UseCase]s are bound to + /// is returned. + Future bindToLifecycleFromInstances( + ProcessCameraProvider instance, + CameraSelector cameraSelector, + List useCases, + ) async { + final int identifier = getProcessCameraProviderIdentifier(instance); + final List useCaseIds = useCases + .map((UseCase useCase) => instanceManager.getIdentifier(useCase)!) + .toList(); + + final int cameraIdentifier = await bindToLifecycle( + identifier, + instanceManager.getIdentifier(cameraSelector)!, + useCaseIds, + ); + return instanceManager.getInstanceWithWeakReference(cameraIdentifier)! + as Camera; + } + + /// Unbinds specified [UseCase]s from the lifecycle of the camera which the + /// provided [ProcessCameraProvider] instance tracks. + void unbindFromInstances( + ProcessCameraProvider instance, + List useCases, + ) { + final int identifier = getProcessCameraProviderIdentifier(instance); + final List useCaseIds = useCases + .map((UseCase useCase) => instanceManager.getIdentifier(useCase)!) + .toList(); + + unbind(identifier, useCaseIds); + } + + /// Unbinds all previously bound [UseCase]s from the lifecycle of the camera + /// which the provided [ProcessCameraProvider] instance tracks. + void unbindAllFromInstances(ProcessCameraProvider instance) { + final int identifier = getProcessCameraProviderIdentifier(instance); + unbindAll(identifier); + } } /// Flutter API Implementation of [ProcessCameraProvider]. diff --git a/packages/camera/camera_android_camerax/lib/src/use_case.dart b/packages/camera/camera_android_camerax/lib/src/use_case.dart new file mode 100644 index 000000000000..f8910d9c5347 --- /dev/null +++ b/packages/camera/camera_android_camerax/lib/src/use_case.dart @@ -0,0 +1,14 @@ +// Copyright 2013 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. + +import 'java_object.dart'; + +/// An object representing the different functionalitites of the camera. +/// +/// See https://developer.android.com/reference/androidx/camera/core/UseCase. +class UseCase extends JavaObject { + /// Creates a detached [UseCase]. + UseCase.detached({super.binaryMessenger, super.instanceManager}) + : super.detached(); +} diff --git a/packages/camera/camera_android_camerax/pigeons/camerax_library.dart b/packages/camera/camera_android_camerax/pigeons/camerax_library.dart index 4d7d96910246..edd2059e162b 100644 --- a/packages/camera/camera_android_camerax/pigeons/camerax_library.dart +++ b/packages/camera/camera_android_camerax/pigeons/camerax_library.dart @@ -64,9 +64,21 @@ abstract class ProcessCameraProviderHostApi { int getInstance(); List getAvailableCameraInfos(int identifier); + + int bindToLifecycle( + int identifier, int cameraSelectorIdentifier, List useCaseIds); + + void unbind(int identifier, List useCaseIds); + + void unbindAll(int identifier); } @FlutterApi() abstract class ProcessCameraProviderFlutterApi { void create(int identifier); } + +@FlutterApi() +abstract class CameraFlutterApi { + void create(int identifier); +} diff --git a/packages/camera/camera_android_camerax/test/camera_info_test.mocks.dart b/packages/camera/camera_android_camerax/test/camera_info_test.mocks.dart index e1f1e3ca9e9b..63ec03c30083 100644 --- a/packages/camera/camera_android_camerax/test/camera_info_test.mocks.dart +++ b/packages/camera/camera_android_camerax/test/camera_info_test.mocks.dart @@ -1,4 +1,4 @@ -// Mocks generated by Mockito 5.3.0 from annotations +// Mocks generated by Mockito 5.3.2 from annotations // in camera_android_camerax/test/camera_info_test.dart. // Do not manually edit this file. @@ -29,6 +29,10 @@ class MockTestCameraInfoHostApi extends _i1.Mock @override int getSensorRotationDegrees(int? identifier) => (super.noSuchMethod( - Invocation.method(#getSensorRotationDegrees, [identifier]), - returnValue: 0) as int); + Invocation.method( + #getSensorRotationDegrees, + [identifier], + ), + returnValue: 0, + ) as int); } diff --git a/packages/camera/camera_android_camerax/test/camera_selector_test.mocks.dart b/packages/camera/camera_android_camerax/test/camera_selector_test.mocks.dart index 456db1eaf822..bb08c82514a5 100644 --- a/packages/camera/camera_android_camerax/test/camera_selector_test.mocks.dart +++ b/packages/camera/camera_android_camerax/test/camera_selector_test.mocks.dart @@ -1,4 +1,4 @@ -// Mocks generated by Mockito 5.3.0 from annotations +// Mocks generated by Mockito 5.3.2 from annotations // in camera_android_camerax/test/camera_selector_test.dart. // Do not manually edit this file. @@ -28,11 +28,33 @@ class MockTestCameraSelectorHostApi extends _i1.Mock } @override - void create(int? identifier, int? lensFacing) => - super.noSuchMethod(Invocation.method(#create, [identifier, lensFacing]), - returnValueForMissingStub: null); + void create( + int? identifier, + int? lensFacing, + ) => + super.noSuchMethod( + Invocation.method( + #create, + [ + identifier, + lensFacing, + ], + ), + returnValueForMissingStub: null, + ); @override - List filter(int? identifier, List? cameraInfoIds) => (super - .noSuchMethod(Invocation.method(#filter, [identifier, cameraInfoIds]), - returnValue: []) as List); + List filter( + int? identifier, + List? cameraInfoIds, + ) => + (super.noSuchMethod( + Invocation.method( + #filter, + [ + identifier, + cameraInfoIds, + ], + ), + returnValue: [], + ) as List); } diff --git a/packages/camera/camera_android_camerax/test/camera_test.dart b/packages/camera/camera_android_camerax/test/camera_test.dart new file mode 100644 index 000000000000..c2948282dcf1 --- /dev/null +++ b/packages/camera/camera_android_camerax/test/camera_test.dart @@ -0,0 +1,26 @@ +// Copyright 2013 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. + +import 'package:camera_android_camerax/src/camera.dart'; +import 'package:camera_android_camerax/src/instance_manager.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('Camera', () { + test('flutterApiCreateTest', () { + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: (_) {}, + ); + final CameraFlutterApiImpl flutterApi = CameraFlutterApiImpl( + instanceManager: instanceManager, + ); + + flutterApi.create(0); + + expect(instanceManager.getInstanceWithWeakReference(0), isA()); + }); + }); +} diff --git a/packages/camera/camera_android_camerax/test/process_camera_provider_test.dart b/packages/camera/camera_android_camerax/test/process_camera_provider_test.dart index 65e7d00ddaea..c4f56f677a58 100644 --- a/packages/camera/camera_android_camerax/test/process_camera_provider_test.dart +++ b/packages/camera/camera_android_camerax/test/process_camera_provider_test.dart @@ -2,9 +2,12 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:camera_android_camerax/src/camera.dart'; import 'package:camera_android_camerax/src/camera_info.dart'; +import 'package:camera_android_camerax/src/camera_selector.dart'; import 'package:camera_android_camerax/src/instance_manager.dart'; import 'package:camera_android_camerax/src/process_camera_provider.dart'; +import 'package:camera_android_camerax/src/use_case.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; @@ -78,6 +81,114 @@ void main() { verify(mockApi.getAvailableCameraInfos(0)); }); + test('bindToLifecycleTest', () async { + final MockTestProcessCameraProviderHostApi mockApi = + MockTestProcessCameraProviderHostApi(); + TestProcessCameraProviderHostApi.setup(mockApi); + + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: (_) {}, + ); + final ProcessCameraProvider processCameraProvider = + ProcessCameraProvider.detached( + instanceManager: instanceManager, + ); + final CameraSelector fakeCameraSelector = + CameraSelector.detached(instanceManager: instanceManager); + final UseCase fakeUseCase = + UseCase.detached(instanceManager: instanceManager); + final Camera fakeCamera = + Camera.detached(instanceManager: instanceManager); + + instanceManager.addHostCreatedInstance( + processCameraProvider, + 0, + onCopy: (_) => ProcessCameraProvider.detached(), + ); + instanceManager.addHostCreatedInstance( + fakeCameraSelector, + 1, + onCopy: (_) => CameraSelector.detached(), + ); + instanceManager.addHostCreatedInstance( + fakeUseCase, + 2, + onCopy: (_) => UseCase.detached(), + ); + instanceManager.addHostCreatedInstance( + fakeCamera, + 3, + onCopy: (_) => Camera.detached(), + ); + + when(mockApi.bindToLifecycle(0, 1, [2])).thenReturn(3); + expect( + await processCameraProvider + .bindToLifecycle(fakeCameraSelector, [fakeUseCase]), + equals(fakeCamera)); + verify(mockApi.bindToLifecycle(0, 1, [2])); + }); + + test('unbindTest', () async { + final MockTestProcessCameraProviderHostApi mockApi = + MockTestProcessCameraProviderHostApi(); + TestProcessCameraProviderHostApi.setup(mockApi); + + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: (_) {}, + ); + final ProcessCameraProvider processCameraProvider = + ProcessCameraProvider.detached( + instanceManager: instanceManager, + ); + final UseCase fakeUseCase = + UseCase.detached(instanceManager: instanceManager); + + instanceManager.addHostCreatedInstance( + processCameraProvider, + 0, + onCopy: (_) => ProcessCameraProvider.detached(), + ); + instanceManager.addHostCreatedInstance( + fakeUseCase, + 1, + onCopy: (_) => UseCase.detached(), + ); + + processCameraProvider.unbind([fakeUseCase]); + verify(mockApi.unbind(0, [1])); + }); + + test('unbindAllTest', () async { + final MockTestProcessCameraProviderHostApi mockApi = + MockTestProcessCameraProviderHostApi(); + TestProcessCameraProviderHostApi.setup(mockApi); + + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: (_) {}, + ); + final ProcessCameraProvider processCameraProvider = + ProcessCameraProvider.detached( + instanceManager: instanceManager, + ); + final UseCase fakeUseCase = + UseCase.detached(instanceManager: instanceManager); + + instanceManager.addHostCreatedInstance( + processCameraProvider, + 0, + onCopy: (_) => ProcessCameraProvider.detached(), + ); + instanceManager.addHostCreatedInstance( + fakeUseCase, + 1, + onCopy: (_) => UseCase.detached(), + ); + + processCameraProvider.unbind([fakeUseCase]); + verify(mockApi.unbind(0, [1])); + }); + test('flutterApiCreateTest', () { final InstanceManager instanceManager = InstanceManager( onWeakReferenceRemoved: (_) {}, diff --git a/packages/camera/camera_android_camerax/test/process_camera_provider_test.mocks.dart b/packages/camera/camera_android_camerax/test/process_camera_provider_test.mocks.dart index 9fcfe690c062..7b0ca76dd1a0 100644 --- a/packages/camera/camera_android_camerax/test/process_camera_provider_test.mocks.dart +++ b/packages/camera/camera_android_camerax/test/process_camera_provider_test.mocks.dart @@ -1,4 +1,4 @@ -// Mocks generated by Mockito 5.3.0 from annotations +// Mocks generated by Mockito 5.3.2 from annotations // in camera_android_camerax/test/process_camera_provider_test.dart. // Do not manually edit this file. @@ -30,11 +30,59 @@ class MockTestProcessCameraProviderHostApi extends _i1.Mock } @override - _i3.Future getInstance() => - (super.noSuchMethod(Invocation.method(#getInstance, []), - returnValue: _i3.Future.value(0)) as _i3.Future); + _i3.Future getInstance() => (super.noSuchMethod( + Invocation.method( + #getInstance, + [], + ), + returnValue: _i3.Future.value(0), + ) as _i3.Future); @override List getAvailableCameraInfos(int? identifier) => (super.noSuchMethod( - Invocation.method(#getAvailableCameraInfos, [identifier]), - returnValue: []) as List); + Invocation.method( + #getAvailableCameraInfos, + [identifier], + ), + returnValue: [], + ) as List); + @override + int bindToLifecycle( + int? identifier, + int? cameraSelectorIdentifier, + List? useCaseIds, + ) => + (super.noSuchMethod( + Invocation.method( + #bindToLifecycle, + [ + identifier, + cameraSelectorIdentifier, + useCaseIds, + ], + ), + returnValue: 0, + ) as int); + @override + void unbind( + int? identifier, + List? useCaseIds, + ) => + super.noSuchMethod( + Invocation.method( + #unbind, + [ + identifier, + useCaseIds, + ], + ), + returnValueForMissingStub: null, + ); + @override + void unbindAll(int? identifier) => super.noSuchMethod( + Invocation.method( + #unbindAll, + [identifier], + ), + returnValueForMissingStub: null, + ); } diff --git a/packages/camera/camera_android_camerax/test/test_camerax_library.pigeon.dart b/packages/camera/camera_android_camerax/test/test_camerax_library.pigeon.dart index 2196b73d7fdb..c6afe067a3b6 100644 --- a/packages/camera/camera_android_camerax/test/test_camerax_library.pigeon.dart +++ b/packages/camera/camera_android_camerax/test/test_camerax_library.pigeon.dart @@ -146,6 +146,10 @@ abstract class TestProcessCameraProviderHostApi { Future getInstance(); List getAvailableCameraInfos(int identifier); + int bindToLifecycle( + int identifier, int cameraSelectorIdentifier, List useCaseIds); + void unbind(int identifier, List useCaseIds); + void unbindAll(int identifier); static void setup(TestProcessCameraProviderHostApi? api, {BinaryMessenger? binaryMessenger}) { { @@ -183,5 +187,75 @@ abstract class TestProcessCameraProviderHostApi { }); } } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.ProcessCameraProviderHostApi.bindToLifecycle', + codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMockMessageHandler(null); + } else { + channel.setMockMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.ProcessCameraProviderHostApi.bindToLifecycle was null.'); + final List args = (message as List?)!; + final int? arg_identifier = (args[0] as int?); + assert(arg_identifier != null, + 'Argument for dev.flutter.pigeon.ProcessCameraProviderHostApi.bindToLifecycle was null, expected non-null int.'); + final int? arg_cameraSelectorIdentifier = (args[1] as int?); + assert(arg_cameraSelectorIdentifier != null, + 'Argument for dev.flutter.pigeon.ProcessCameraProviderHostApi.bindToLifecycle was null, expected non-null int.'); + final List? arg_useCaseIds = + (args[2] as List?)?.cast(); + assert(arg_useCaseIds != null, + 'Argument for dev.flutter.pigeon.ProcessCameraProviderHostApi.bindToLifecycle was null, expected non-null List.'); + final int output = api.bindToLifecycle( + arg_identifier!, arg_cameraSelectorIdentifier!, arg_useCaseIds!); + return {'result': output}; + }); + } + } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.ProcessCameraProviderHostApi.unbind', codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMockMessageHandler(null); + } else { + channel.setMockMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.ProcessCameraProviderHostApi.unbind was null.'); + final List args = (message as List?)!; + final int? arg_identifier = (args[0] as int?); + assert(arg_identifier != null, + 'Argument for dev.flutter.pigeon.ProcessCameraProviderHostApi.unbind was null, expected non-null int.'); + final List? arg_useCaseIds = + (args[1] as List?)?.cast(); + assert(arg_useCaseIds != null, + 'Argument for dev.flutter.pigeon.ProcessCameraProviderHostApi.unbind was null, expected non-null List.'); + api.unbind(arg_identifier!, arg_useCaseIds!); + return {}; + }); + } + } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.ProcessCameraProviderHostApi.unbindAll', codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMockMessageHandler(null); + } else { + channel.setMockMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.ProcessCameraProviderHostApi.unbindAll was null.'); + final List args = (message as List?)!; + final int? arg_identifier = (args[0] as int?); + assert(arg_identifier != null, + 'Argument for dev.flutter.pigeon.ProcessCameraProviderHostApi.unbindAll was null, expected non-null int.'); + api.unbindAll(arg_identifier!); + return {}; + }); + } + } } } From af065a6a1a72bd20c4a00d8ba41fefa868aec265 Mon Sep 17 00:00:00 2001 From: stuartmorgan Date: Thu, 26 Jan 2023 10:28:41 -0800 Subject: [PATCH 039/130] [tool/ci] Add minimum supported SDK validation (#7028) Adds options to `pubspec.yaml` to check that the minimum supported SDK range for Flutter/Dart is at least a given version, to add CI enforcement that we're updating all of our support claims when we update our tested versions (rather than it being something we have to remember to do), and enables it in CI. As part of enabling it, fixes some violations: - path_provider_foundation had been temporarily dropped back to 2.10 as part of pushing out a regression fix. - a number of examples were missing Flutter constraints even though they used Flutter. - the non-Flutter `plugin_platform_interface` package hadn't been update since I hadn't thought about Dart-only constraints in the past. --- .cirrus.yml | 5 +- .../file_selector/example/pubspec.yaml | 1 + .../file_selector_ios/example/pubspec.yaml | 3 +- .../file_selector_linux/example/pubspec.yaml | 1 + .../example/pubspec.yaml | 1 + .../path_provider_foundation/CHANGELOG.md | 4 + .../path_provider_foundation/pubspec.yaml | 2 +- .../plugin_platform_interface/CHANGELOG.md | 4 + .../plugin_platform_interface/pubspec.yaml | 2 +- .../example/pubspec.yaml | 1 + .../webview_flutter_web/example/pubspec.yaml | 1 + .../example/pubspec.yaml | 1 + script/tool/CHANGELOG.md | 5 + .../tool/lib/src/pubspec_check_command.dart | 76 +++++++- script/tool/pubspec.yaml | 2 +- .../tool/test/pubspec_check_command_test.dart | 173 +++++++++++++++++- 16 files changed, 270 insertions(+), 12 deletions(-) diff --git a/.cirrus.yml b/.cirrus.yml index 85f8a2a115dd..4a5d604a081f 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -101,7 +101,9 @@ task: always: format_script: ./script/tool_runner.sh format --fail-on-change license_script: $PLUGIN_TOOL_COMMAND license-check - pubspec_script: ./script/tool_runner.sh pubspec-check + # The major and minor versions here should match the lowest version + # analyzed in legacy_version_analyze. + pubspec_script: ./script/tool_runner.sh pubspec-check --min-min-flutter-version=3.0.0 --min-min-dart-version=2.17.0 readme_script: - ./script/tool_runner.sh readme-check # Re-run with --require-excerpts, skipping packages that still need @@ -171,6 +173,7 @@ task: - name: legacy_version_analyze depends_on: analyze matrix: + # Change the arguments to pubspec-check when changing these values. env: CHANNEL: "3.0.5" DART_VERSION: "2.17.6" diff --git a/packages/file_selector/file_selector/example/pubspec.yaml b/packages/file_selector/file_selector/example/pubspec.yaml index 011d95874ae4..ff9d6d0d2e17 100644 --- a/packages/file_selector/file_selector/example/pubspec.yaml +++ b/packages/file_selector/file_selector/example/pubspec.yaml @@ -6,6 +6,7 @@ version: 1.0.0+1 environment: sdk: ">=2.12.0 <3.0.0" + flutter: ">=3.0.0" dependencies: file_selector: diff --git a/packages/file_selector/file_selector_ios/example/pubspec.yaml b/packages/file_selector/file_selector_ios/example/pubspec.yaml index 5a2eaa6f7dcd..175ec6c6e7d0 100644 --- a/packages/file_selector/file_selector_ios/example/pubspec.yaml +++ b/packages/file_selector/file_selector_ios/example/pubspec.yaml @@ -5,6 +5,7 @@ version: 1.0.0 environment: sdk: ">=2.14.4 <3.0.0" + flutter: ">=3.0.0" dependencies: # The following adds the Cupertino Icons font to your application. @@ -28,4 +29,4 @@ dev_dependencies: sdk: flutter flutter: - uses-material-design: true \ No newline at end of file + uses-material-design: true diff --git a/packages/file_selector/file_selector_linux/example/pubspec.yaml b/packages/file_selector/file_selector_linux/example/pubspec.yaml index 912a082bd602..f90d1c88ef97 100644 --- a/packages/file_selector/file_selector_linux/example/pubspec.yaml +++ b/packages/file_selector/file_selector_linux/example/pubspec.yaml @@ -5,6 +5,7 @@ version: 1.0.0+1 environment: sdk: ">=2.12.0 <3.0.0" + flutter: ">=3.0.0" dependencies: file_selector_linux: diff --git a/packages/flutter_plugin_android_lifecycle/example/pubspec.yaml b/packages/flutter_plugin_android_lifecycle/example/pubspec.yaml index e732497eee95..4c97e6c44cd1 100644 --- a/packages/flutter_plugin_android_lifecycle/example/pubspec.yaml +++ b/packages/flutter_plugin_android_lifecycle/example/pubspec.yaml @@ -4,6 +4,7 @@ publish_to: none environment: sdk: ">=2.12.0 <3.0.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/path_provider/path_provider_foundation/CHANGELOG.md b/packages/path_provider/path_provider_foundation/CHANGELOG.md index 17e45d37a2ba..7adb04f4c984 100644 --- a/packages/path_provider/path_provider_foundation/CHANGELOG.md +++ b/packages/path_provider/path_provider_foundation/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum supported Flutter version to 3.0. + ## 2.1.1 * Fixes a regression in the path retured by `getApplicationSupportDirectory` on iOS. diff --git a/packages/path_provider/path_provider_foundation/pubspec.yaml b/packages/path_provider/path_provider_foundation/pubspec.yaml index 9ceb115ccfe1..30dd655acc00 100644 --- a/packages/path_provider/path_provider_foundation/pubspec.yaml +++ b/packages/path_provider/path_provider_foundation/pubspec.yaml @@ -6,7 +6,7 @@ version: 2.1.1 environment: sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.10.0" + flutter: ">=3.0.0" flutter: plugin: diff --git a/packages/plugin_platform_interface/CHANGELOG.md b/packages/plugin_platform_interface/CHANGELOG.md index 0b5a6b63a52f..93e45c814668 100644 --- a/packages/plugin_platform_interface/CHANGELOG.md +++ b/packages/plugin_platform_interface/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Updates minimum supported Dart version. + ## 2.1.3 * Minor fixes for new analysis options. diff --git a/packages/plugin_platform_interface/pubspec.yaml b/packages/plugin_platform_interface/pubspec.yaml index 6a4bc488693b..25189d942f84 100644 --- a/packages/plugin_platform_interface/pubspec.yaml +++ b/packages/plugin_platform_interface/pubspec.yaml @@ -18,7 +18,7 @@ issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+ version: 2.1.3 environment: - sdk: ">=2.12.0 <3.0.0" + sdk: ">=2.17.0 <3.0.0" dependencies: meta: ^1.3.0 diff --git a/packages/webview_flutter/webview_flutter_android/example/pubspec.yaml b/packages/webview_flutter/webview_flutter_android/example/pubspec.yaml index 0daacb07b13f..0fc0daf84118 100644 --- a/packages/webview_flutter/webview_flutter_android/example/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_android/example/pubspec.yaml @@ -4,6 +4,7 @@ publish_to: none environment: sdk: ">=2.14.0 <3.0.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/webview_flutter/webview_flutter_web/example/pubspec.yaml b/packages/webview_flutter/webview_flutter_web/example/pubspec.yaml index 782817eb7100..4685135acdf1 100644 --- a/packages/webview_flutter/webview_flutter_web/example/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_web/example/pubspec.yaml @@ -4,6 +4,7 @@ publish_to: none environment: sdk: ">=2.14.0 <3.0.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/pubspec.yaml b/packages/webview_flutter/webview_flutter_wkwebview/example/pubspec.yaml index 7ccb302a843b..718eb282018b 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/pubspec.yaml @@ -4,6 +4,7 @@ publish_to: none environment: sdk: ">=2.12.0 <3.0.0" + flutter: ">=3.0.0" dependencies: flutter: diff --git a/script/tool/CHANGELOG.md b/script/tool/CHANGELOG.md index 55b5aeb7222a..34def6ecf676 100644 --- a/script/tool/CHANGELOG.md +++ b/script/tool/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.13.4 + +* Adds the ability to validate minimum supported Dart/Flutter versions in + `pubspec-check`. + ## 0.13.3 * Renames `podspecs` to `podspec-check`. The old name will continue to work. diff --git a/script/tool/lib/src/pubspec_check_command.dart b/script/tool/lib/src/pubspec_check_command.dart index 5682ba057688..aefa316a41f6 100644 --- a/script/tool/lib/src/pubspec_check_command.dart +++ b/script/tool/lib/src/pubspec_check_command.dart @@ -5,6 +5,7 @@ import 'package:file/file.dart'; import 'package:git/git.dart'; import 'package:platform/platform.dart'; +import 'package:pub_semver/pub_semver.dart'; import 'package:yaml/yaml.dart'; import 'common/core.dart'; @@ -29,7 +30,23 @@ class PubspecCheckCommand extends PackageLoopingCommand { processRunner: processRunner, platform: platform, gitDir: gitDir, - ); + ) { + argParser.addOption( + _minMinDartVersionFlag, + help: + 'The minimum Dart version to allow as the minimum SDK constraint.\n\n' + 'This is only enforced for non-Flutter packages; Flutter packages ' + 'use --$_minMinFlutterVersionFlag', + ); + argParser.addOption( + _minMinFlutterVersionFlag, + help: + 'The minimum Flutter version to allow as the minimum SDK constraint.', + ); + } + + static const String _minMinDartVersionFlag = 'min-min-dart-version'; + static const String _minMinFlutterVersionFlag = 'min-min-flutter-version'; // Section order for plugins. Because the 'flutter' section is critical // information for plugins, and usually small, it goes near the top unlike in @@ -100,6 +117,24 @@ class PubspecCheckCommand extends PackageLoopingCommand { printError('$listIndentation${sectionOrder.join('\n$listIndentation')}'); } + final String minMinDartVersionString = getStringArg(_minMinDartVersionFlag); + final String minMinFlutterVersionString = + getStringArg(_minMinFlutterVersionFlag); + final String? minVersionError = _checkForMinimumVersionError( + pubspec, + package, + minMinDartVersion: minMinDartVersionString.isEmpty + ? null + : Version.parse(minMinDartVersionString), + minMinFlutterVersion: minMinFlutterVersionString.isEmpty + ? null + : Version.parse(minMinFlutterVersionString), + ); + if (minVersionError != null) { + printError('$indentation$minVersionError'); + passing = false; + } + if (isPlugin) { final String? implementsError = _checkForImplementsError(pubspec, package: package); @@ -320,4 +355,43 @@ class PubspecCheckCommand extends PackageLoopingCommand { final String suffix = packageName.substring(parentName.length); return !nonImplementationSuffixes.contains(suffix); } + + /// Validates that a Flutter package has a minimum SDK version constraint of + /// at least [minMinFlutterVersion] (if provided), or that a non-Flutter + /// package has a minimum SDK version constraint of [minMinDartVersion] + /// (if provided). + /// + /// Returns an error string if validation fails. + String? _checkForMinimumVersionError( + Pubspec pubspec, + RepositoryPackage package, { + Version? minMinDartVersion, + Version? minMinFlutterVersion, + }) { + final VersionConstraint? dartConstraint = pubspec.environment?['sdk']; + final VersionConstraint? flutterConstraint = + pubspec.environment?['flutter']; + + if (flutterConstraint != null) { + // Validate Flutter packages against the Flutter requirement. + if (minMinFlutterVersion != null) { + final Version? constraintMin = + flutterConstraint is VersionRange ? flutterConstraint.min : null; + if ((constraintMin ?? Version(0, 0, 0)) < minMinFlutterVersion) { + return 'Minimum allowed Flutter version $constraintMin is less than $minMinFlutterVersion'; + } + } + } else { + // Validate non-Flutter packages against the Dart requirement. + if (minMinDartVersion != null) { + final Version? constraintMin = + dartConstraint is VersionRange ? dartConstraint.min : null; + if ((constraintMin ?? Version(0, 0, 0)) < minMinDartVersion) { + return 'Minimum allowed Dart version $constraintMin is less than $minMinDartVersion'; + } + } + } + + return null; + } } diff --git a/script/tool/pubspec.yaml b/script/tool/pubspec.yaml index abf2a61f4cf0..a8df2a9cd23a 100644 --- a/script/tool/pubspec.yaml +++ b/script/tool/pubspec.yaml @@ -1,7 +1,7 @@ name: flutter_plugin_tools description: Productivity utils for flutter/plugins and flutter/packages repository: https://github.com/flutter/plugins/tree/main/script/tool -version: 0.13.3 +version: 0.13.4 dependencies: args: ^2.1.0 diff --git a/script/tool/test/pubspec_check_command_test.dart b/script/tool/test/pubspec_check_command_test.dart index 2c254ca94984..7a9c0cec7cbc 100644 --- a/script/tool/test/pubspec_check_command_test.dart +++ b/script/tool/test/pubspec_check_command_test.dart @@ -60,12 +60,16 @@ ${publishable ? '' : "publish_to: 'none'"} '''; } -String _environmentSection() { - return ''' -environment: - sdk: ">=2.12.0 <3.0.0" - flutter: ">=2.0.0" -'''; +String _environmentSection({ + String dartConstraint = '>=2.12.0 <3.0.0', + String? flutterConstraint = '>=2.0.0', +}) { + return [ + 'environment:', + ' sdk: "$dartConstraint"', + if (flutterConstraint != null) ' flutter: "$flutterConstraint"', + '', + ].join('\n'); } String _flutterSection({ @@ -931,6 +935,163 @@ ${_devDependenciesSection()} ]), ); }); + + test('fails when a Flutter package has a too-low minimum Flutter version', + () async { + final RepositoryPackage package = createFakePackage( + 'a_package', packagesDir, + isFlutter: true, examples: []); + + package.pubspecFile.writeAsStringSync(''' +${_headerSection('a_package')} +${_environmentSection(flutterConstraint: '>=2.10.0')} +${_dependenciesSection()} +'''); + + Error? commandError; + final List output = await runCapturingPrint(runner, [ + 'pubspec-check', + '--min-min-flutter-version', + '3.0.0' + ], errorHandler: (Error e) { + commandError = e; + }); + + expect(commandError, isA()); + expect( + output, + containsAllInOrder([ + contains('Minimum allowed Flutter version 2.10.0 is less than 3.0.0'), + ]), + ); + }); + + test( + 'passes when a Flutter package requires exactly the minimum Flutter version', + () async { + final RepositoryPackage package = createFakePackage( + 'a_package', packagesDir, + isFlutter: true, examples: []); + + package.pubspecFile.writeAsStringSync(''' +${_headerSection('a_package')} +${_environmentSection(flutterConstraint: '>=3.0.0')} +${_dependenciesSection()} +'''); + + final List output = await runCapturingPrint(runner, + ['pubspec-check', '--min-min-flutter-version', '3.0.0']); + + expect( + output, + containsAllInOrder([ + contains('Running for a_package...'), + contains('No issues found!'), + ]), + ); + }); + + test( + 'passes when a Flutter package requires a higher minimum Flutter version', + () async { + final RepositoryPackage package = createFakePackage( + 'a_package', packagesDir, + isFlutter: true, examples: []); + + package.pubspecFile.writeAsStringSync(''' +${_headerSection('a_package')} +${_environmentSection(flutterConstraint: '>=3.3.0')} +${_dependenciesSection()} +'''); + + final List output = await runCapturingPrint(runner, + ['pubspec-check', '--min-min-flutter-version', '3.0.0']); + + expect( + output, + containsAllInOrder([ + contains('Running for a_package...'), + contains('No issues found!'), + ]), + ); + }); + + test('fails when a non-Flutter package has a too-low minimum Dart version', + () async { + final RepositoryPackage package = + createFakePackage('a_package', packagesDir, examples: []); + + package.pubspecFile.writeAsStringSync(''' +${_headerSection('a_package')} +${_environmentSection(dartConstraint: '>=2.14.0 <3.0.0', flutterConstraint: null)} +${_dependenciesSection()} +'''); + + Error? commandError; + final List output = await runCapturingPrint( + runner, ['pubspec-check', '--min-min-dart-version', '2.17.0'], + errorHandler: (Error e) { + commandError = e; + }); + + expect(commandError, isA()); + expect( + output, + containsAllInOrder([ + contains('Minimum allowed Dart version 2.14.0 is less than 2.17.0'), + ]), + ); + }); + + test( + 'passes when a non-Flutter package requires exactly the minimum Dart version', + () async { + final RepositoryPackage package = createFakePackage( + 'a_package', packagesDir, + isFlutter: true, examples: []); + + package.pubspecFile.writeAsStringSync(''' +${_headerSection('a_package')} +${_environmentSection(dartConstraint: '>=2.17.0 <3.0.0', flutterConstraint: null)} +${_dependenciesSection()} +'''); + + final List output = await runCapturingPrint(runner, + ['pubspec-check', '--min-min-dart-version', '2.17.0']); + + expect( + output, + containsAllInOrder([ + contains('Running for a_package...'), + contains('No issues found!'), + ]), + ); + }); + + test( + 'passes when a non-Flutter package requires a higher minimum Dart version', + () async { + final RepositoryPackage package = createFakePackage( + 'a_package', packagesDir, + isFlutter: true, examples: []); + + package.pubspecFile.writeAsStringSync(''' +${_headerSection('a_package')} +${_environmentSection(dartConstraint: '>=2.18.0 <3.0.0', flutterConstraint: null)} +${_dependenciesSection()} +'''); + + final List output = await runCapturingPrint(runner, + ['pubspec-check', '--min-min-dart-version', '2.17.0']); + + expect( + output, + containsAllInOrder([ + contains('Running for a_package...'), + contains('No issues found!'), + ]), + ); + }); }); group('test pubspec_check_command on Windows', () { From ff84c44a5ddbfe10e96f16fe6c09157d36cc2867 Mon Sep 17 00:00:00 2001 From: Camille Simon <43054281+camsim99@users.noreply.github.com> Date: Thu, 26 Jan 2023 10:31:05 -0800 Subject: [PATCH 040/130] [camera] Add back Optional type for nullable CameraController orientations (#6911) * Add flag * Add missing comment * Add tests * Bump versions * Stage changelog changes * Revert "Fix examples analyze" This reverts commit 4db1858a29136f3fb07a223d94d7e68b6b8d4b7d. * Revert "[camera] Remove deprecated Optional type (#6870)" This reverts commit 3d8b73bf08bf746bcbcdd219eb87ced572cc529b. * Add back optional * Edit changelog * Fix semicolon * Add ) --- packages/camera/camera/CHANGELOG.md | 4 + .../camera/lib/src/camera_controller.dart | 155 ++++++++++++++++-- packages/camera/camera/pubspec.yaml | 2 +- .../camera/test/camera_preview_test.dart | 17 +- packages/camera/camera/test/camera_test.dart | 3 +- packages/camera/camera_android/CHANGELOG.md | 3 +- .../example/lib/camera_controller.dart | 149 +++++++++++++++-- packages/camera/camera_android/pubspec.yaml | 2 +- .../camera/camera_avfoundation/CHANGELOG.md | 3 +- .../example/lib/camera_controller.dart | 153 +++++++++++++++-- .../camera/camera_avfoundation/pubspec.yaml | 2 +- 11 files changed, 436 insertions(+), 57 deletions(-) diff --git a/packages/camera/camera/CHANGELOG.md b/packages/camera/camera/CHANGELOG.md index f7db10b19c99..13c00402449a 100644 --- a/packages/camera/camera/CHANGELOG.md +++ b/packages/camera/camera/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.10.3 + +* Adds back use of Optional type. + ## 0.10.2+1 * Updates code for stricter lint checks. diff --git a/packages/camera/camera/lib/src/camera_controller.dart b/packages/camera/camera/lib/src/camera_controller.dart index b201074f3810..7a396c1589f9 100644 --- a/packages/camera/camera/lib/src/camera_controller.dart +++ b/packages/camera/camera/lib/src/camera_controller.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'dart:async'; +import 'dart:collection'; import 'dart:math'; import 'package:camera_platform_interface/camera_platform_interface.dart'; @@ -160,10 +161,10 @@ class CameraValue { bool? exposurePointSupported, bool? focusPointSupported, DeviceOrientation? deviceOrientation, - DeviceOrientation? lockedCaptureOrientation, - DeviceOrientation? recordingOrientation, + Optional? lockedCaptureOrientation, + Optional? recordingOrientation, bool? isPreviewPaused, - DeviceOrientation? previewPauseOrientation, + Optional? previewPauseOrientation, }) { return CameraValue( isInitialized: isInitialized ?? this.isInitialized, @@ -180,12 +181,16 @@ class CameraValue { exposurePointSupported ?? this.exposurePointSupported, focusPointSupported: focusPointSupported ?? this.focusPointSupported, deviceOrientation: deviceOrientation ?? this.deviceOrientation, - lockedCaptureOrientation: - lockedCaptureOrientation ?? this.lockedCaptureOrientation, - recordingOrientation: recordingOrientation ?? this.recordingOrientation, + lockedCaptureOrientation: lockedCaptureOrientation == null + ? this.lockedCaptureOrientation + : lockedCaptureOrientation.orNull, + recordingOrientation: recordingOrientation == null + ? this.recordingOrientation + : recordingOrientation.orNull, isPreviewPaused: isPreviewPaused ?? this.isPreviewPaused, - previewPauseOrientation: - previewPauseOrientation ?? this.previewPauseOrientation, + previewPauseOrientation: previewPauseOrientation == null + ? this.previewPauseOrientation + : previewPauseOrientation.orNull, ); } @@ -353,8 +358,8 @@ class CameraController extends ValueNotifier { await CameraPlatform.instance.pausePreview(_cameraId); value = value.copyWith( isPreviewPaused: true, - previewPauseOrientation: - value.lockedCaptureOrientation ?? value.deviceOrientation); + previewPauseOrientation: Optional.of( + value.lockedCaptureOrientation ?? value.deviceOrientation)); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } @@ -367,7 +372,9 @@ class CameraController extends ValueNotifier { } try { await CameraPlatform.instance.resumePreview(_cameraId); - value = value.copyWith(isPreviewPaused: false); + value = value.copyWith( + isPreviewPaused: false, + previewPauseOrientation: const Optional.absent()); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } @@ -498,9 +505,9 @@ class CameraController extends ValueNotifier { value = value.copyWith( isRecordingVideo: true, isRecordingPaused: false, - isStreamingImages: onAvailable != null, - recordingOrientation: - value.lockedCaptureOrientation ?? value.deviceOrientation); + recordingOrientation: Optional.of( + value.lockedCaptureOrientation ?? value.deviceOrientation), + isStreamingImages: onAvailable != null); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } @@ -525,7 +532,10 @@ class CameraController extends ValueNotifier { try { final XFile file = await CameraPlatform.instance.stopVideoRecording(_cameraId); - value = value.copyWith(isRecordingVideo: false); + value = value.copyWith( + isRecordingVideo: false, + recordingOrientation: const Optional.absent(), + ); return file; } on PlatformException catch (e) { throw CameraException(e.code, e.message); @@ -743,7 +753,8 @@ class CameraController extends ValueNotifier { await CameraPlatform.instance.lockCaptureOrientation( _cameraId, orientation ?? value.deviceOrientation); value = value.copyWith( - lockedCaptureOrientation: orientation ?? value.deviceOrientation); + lockedCaptureOrientation: Optional.of( + orientation ?? value.deviceOrientation)); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } @@ -763,7 +774,8 @@ class CameraController extends ValueNotifier { Future unlockCaptureOrientation() async { try { await CameraPlatform.instance.unlockCaptureOrientation(_cameraId); - value = value.copyWith(); + value = value.copyWith( + lockedCaptureOrientation: const Optional.absent()); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } @@ -834,3 +846,112 @@ class CameraController extends ValueNotifier { } } } + +/// A value that might be absent. +/// +/// Used to represent [DeviceOrientation]s that are optional but also able +/// to be cleared. +@immutable +class Optional extends IterableBase { + /// Constructs an empty Optional. + const Optional.absent() : _value = null; + + /// Constructs an Optional of the given [value]. + /// + /// Throws [ArgumentError] if [value] is null. + Optional.of(T value) : _value = value { + // TODO(cbracken): Delete and make this ctor const once mixed-mode + // execution is no longer around. + ArgumentError.checkNotNull(value); + } + + /// Constructs an Optional of the given [value]. + /// + /// If [value] is null, returns [absent()]. + const Optional.fromNullable(T? value) : _value = value; + + final T? _value; + + /// True when this optional contains a value. + bool get isPresent => _value != null; + + /// True when this optional contains no value. + bool get isNotPresent => _value == null; + + /// Gets the Optional value. + /// + /// Throws [StateError] if [value] is null. + T get value { + if (_value == null) { + throw StateError('value called on absent Optional.'); + } + return _value!; + } + + /// Executes a function if the Optional value is present. + void ifPresent(void Function(T value) ifPresent) { + if (isPresent) { + ifPresent(_value as T); + } + } + + /// Execution a function if the Optional value is absent. + void ifAbsent(void Function() ifAbsent) { + if (!isPresent) { + ifAbsent(); + } + } + + /// Gets the Optional value with a default. + /// + /// The default is returned if the Optional is [absent()]. + /// + /// Throws [ArgumentError] if [defaultValue] is null. + T or(T defaultValue) { + return _value ?? defaultValue; + } + + /// Gets the Optional value, or `null` if there is none. + T? get orNull => _value; + + /// Transforms the Optional value. + /// + /// If the Optional is [absent()], returns [absent()] without applying the transformer. + /// + /// The transformer must not return `null`. If it does, an [ArgumentError] is thrown. + Optional transform(S Function(T value) transformer) { + return _value == null + ? Optional.absent() + : Optional.of(transformer(_value as T)); + } + + /// Transforms the Optional value. + /// + /// If the Optional is [absent()], returns [absent()] without applying the transformer. + /// + /// Returns [absent()] if the transformer returns `null`. + Optional transformNullable(S? Function(T value) transformer) { + return _value == null + ? Optional.absent() + : Optional.fromNullable(transformer(_value as T)); + } + + @override + Iterator get iterator => + isPresent ? [_value as T].iterator : Iterable.empty().iterator; + + /// Delegates to the underlying [value] hashCode. + @override + int get hashCode => _value.hashCode; + + /// Delegates to the underlying [value] operator==. + @override + bool operator ==(Object o) => o is Optional && o._value == _value; + + @override + String toString() { + return _value == null + ? 'Optional { absent }' + : 'Optional { value: $_value }'; + } +} diff --git a/packages/camera/camera/pubspec.yaml b/packages/camera/camera/pubspec.yaml index fb62665e2e39..1b902ab61f0a 100644 --- a/packages/camera/camera/pubspec.yaml +++ b/packages/camera/camera/pubspec.yaml @@ -4,7 +4,7 @@ description: A Flutter plugin for controlling the camera. Supports previewing Dart. repository: https://github.com/flutter/plugins/tree/main/packages/camera/camera issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22 -version: 0.10.2+1 +version: 0.10.3 environment: sdk: ">=2.14.0 <3.0.0" diff --git a/packages/camera/camera/test/camera_preview_test.dart b/packages/camera/camera/test/camera_preview_test.dart index 7c4378749ebc..6677fcf90393 100644 --- a/packages/camera/camera/test/camera_preview_test.dart +++ b/packages/camera/camera/test/camera_preview_test.dart @@ -133,8 +133,11 @@ void main() { isInitialized: true, isRecordingVideo: true, deviceOrientation: DeviceOrientation.portraitUp, - lockedCaptureOrientation: DeviceOrientation.landscapeRight, - recordingOrientation: DeviceOrientation.landscapeLeft, + lockedCaptureOrientation: + const Optional.fromNullable( + DeviceOrientation.landscapeRight), + recordingOrientation: const Optional.fromNullable( + DeviceOrientation.landscapeLeft), previewSize: const Size(480, 640), ); @@ -164,8 +167,11 @@ void main() { controller.value = controller.value.copyWith( isInitialized: true, deviceOrientation: DeviceOrientation.portraitUp, - lockedCaptureOrientation: DeviceOrientation.landscapeRight, - recordingOrientation: DeviceOrientation.landscapeLeft, + lockedCaptureOrientation: + const Optional.fromNullable( + DeviceOrientation.landscapeRight), + recordingOrientation: const Optional.fromNullable( + DeviceOrientation.landscapeLeft), previewSize: const Size(480, 640), ); @@ -195,7 +201,8 @@ void main() { controller.value = controller.value.copyWith( isInitialized: true, deviceOrientation: DeviceOrientation.portraitUp, - recordingOrientation: DeviceOrientation.landscapeLeft, + recordingOrientation: const Optional.fromNullable( + DeviceOrientation.landscapeLeft), previewSize: const Size(480, 640), ); diff --git a/packages/camera/camera/test/camera_test.dart b/packages/camera/camera/test/camera_test.dart index 44a48d160d37..ab8354f7ba05 100644 --- a/packages/camera/camera/test/camera_test.dart +++ b/packages/camera/camera/test/camera_test.dart @@ -1166,7 +1166,8 @@ void main() { cameraController.value = cameraController.value.copyWith( isPreviewPaused: false, deviceOrientation: DeviceOrientation.portraitUp, - lockedCaptureOrientation: DeviceOrientation.landscapeRight); + lockedCaptureOrientation: + Optional.of(DeviceOrientation.landscapeRight)); await cameraController.pausePreview(); diff --git a/packages/camera/camera_android/CHANGELOG.md b/packages/camera/camera_android/CHANGELOG.md index 0aec0be2b442..0cb9957d029d 100644 --- a/packages/camera/camera_android/CHANGELOG.md +++ b/packages/camera/camera_android/CHANGELOG.md @@ -1,5 +1,6 @@ -## NEXT +## 0.10.3 +* Adds back use of Optional type. * Updates minimum Flutter version to 3.0. ## 0.10.2+3 diff --git a/packages/camera/camera_android/example/lib/camera_controller.dart b/packages/camera/camera_android/example/lib/camera_controller.dart index 79bf4e8b01e1..8139dcdb0220 100644 --- a/packages/camera/camera_android/example/lib/camera_controller.dart +++ b/packages/camera/camera_android/example/lib/camera_controller.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'dart:async'; +import 'dart:collection'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:flutter/foundation.dart'; @@ -108,10 +109,10 @@ class CameraValue { bool? exposurePointSupported, bool? focusPointSupported, DeviceOrientation? deviceOrientation, - DeviceOrientation? lockedCaptureOrientation, - DeviceOrientation? recordingOrientation, + Optional? lockedCaptureOrientation, + Optional? recordingOrientation, bool? isPreviewPaused, - DeviceOrientation? previewPauseOrientation, + Optional? previewPauseOrientation, }) { return CameraValue( isInitialized: isInitialized ?? this.isInitialized, @@ -124,12 +125,16 @@ class CameraValue { exposureMode: exposureMode ?? this.exposureMode, focusMode: focusMode ?? this.focusMode, deviceOrientation: deviceOrientation ?? this.deviceOrientation, - lockedCaptureOrientation: - lockedCaptureOrientation ?? this.lockedCaptureOrientation, - recordingOrientation: recordingOrientation ?? this.recordingOrientation, + lockedCaptureOrientation: lockedCaptureOrientation == null + ? this.lockedCaptureOrientation + : lockedCaptureOrientation.orNull, + recordingOrientation: recordingOrientation == null + ? this.recordingOrientation + : recordingOrientation.orNull, isPreviewPaused: isPreviewPaused ?? this.isPreviewPaused, - previewPauseOrientation: - previewPauseOrientation ?? this.previewPauseOrientation, + previewPauseOrientation: previewPauseOrientation == null + ? this.previewPauseOrientation + : previewPauseOrientation.orNull, ); } @@ -257,14 +262,16 @@ class CameraController extends ValueNotifier { await CameraPlatform.instance.pausePreview(_cameraId); value = value.copyWith( isPreviewPaused: true, - previewPauseOrientation: - value.lockedCaptureOrientation ?? value.deviceOrientation); + previewPauseOrientation: Optional.of( + value.lockedCaptureOrientation ?? value.deviceOrientation)); } /// Resumes the current camera preview Future resumePreview() async { await CameraPlatform.instance.resumePreview(_cameraId); - value = value.copyWith(isPreviewPaused: false); + value = value.copyWith( + isPreviewPaused: false, + previewPauseOrientation: const Optional.absent()); } /// Captures an image and returns the file where it was saved. @@ -307,8 +314,8 @@ class CameraController extends ValueNotifier { isRecordingVideo: true, isRecordingPaused: false, isStreamingImages: streamCallback != null, - recordingOrientation: - value.lockedCaptureOrientation ?? value.deviceOrientation); + recordingOrientation: Optional.of( + value.lockedCaptureOrientation ?? value.deviceOrientation)); } /// Stops the video recording and returns the file where it was saved. @@ -324,6 +331,7 @@ class CameraController extends ValueNotifier { value = value.copyWith( isRecordingVideo: false, isRecordingPaused: false, + recordingOrientation: const Optional.absent(), ); return file; } @@ -392,12 +400,16 @@ class CameraController extends ValueNotifier { Future lockCaptureOrientation() async { await CameraPlatform.instance .lockCaptureOrientation(_cameraId, value.deviceOrientation); - value = value.copyWith(lockedCaptureOrientation: value.deviceOrientation); + value = value.copyWith( + lockedCaptureOrientation: + Optional.of(value.deviceOrientation)); } /// Unlocks the capture orientation. Future unlockCaptureOrientation() async { await CameraPlatform.instance.unlockCaptureOrientation(_cameraId); + value = value.copyWith( + lockedCaptureOrientation: const Optional.absent()); } /// Sets the focus mode for taking pictures. @@ -431,3 +443,112 @@ class CameraController extends ValueNotifier { } } } + +/// A value that might be absent. +/// +/// Used to represent [DeviceOrientation]s that are optional but also able +/// to be cleared. +@immutable +class Optional extends IterableBase { + /// Constructs an empty Optional. + const Optional.absent() : _value = null; + + /// Constructs an Optional of the given [value]. + /// + /// Throws [ArgumentError] if [value] is null. + Optional.of(T value) : _value = value { + // TODO(cbracken): Delete and make this ctor const once mixed-mode + // execution is no longer around. + ArgumentError.checkNotNull(value); + } + + /// Constructs an Optional of the given [value]. + /// + /// If [value] is null, returns [absent()]. + const Optional.fromNullable(T? value) : _value = value; + + final T? _value; + + /// True when this optional contains a value. + bool get isPresent => _value != null; + + /// True when this optional contains no value. + bool get isNotPresent => _value == null; + + /// Gets the Optional value. + /// + /// Throws [StateError] if [value] is null. + T get value { + if (_value == null) { + throw StateError('value called on absent Optional.'); + } + return _value!; + } + + /// Executes a function if the Optional value is present. + void ifPresent(void Function(T value) ifPresent) { + if (isPresent) { + ifPresent(_value as T); + } + } + + /// Execution a function if the Optional value is absent. + void ifAbsent(void Function() ifAbsent) { + if (!isPresent) { + ifAbsent(); + } + } + + /// Gets the Optional value with a default. + /// + /// The default is returned if the Optional is [absent()]. + /// + /// Throws [ArgumentError] if [defaultValue] is null. + T or(T defaultValue) { + return _value ?? defaultValue; + } + + /// Gets the Optional value, or `null` if there is none. + T? get orNull => _value; + + /// Transforms the Optional value. + /// + /// If the Optional is [absent()], returns [absent()] without applying the transformer. + /// + /// The transformer must not return `null`. If it does, an [ArgumentError] is thrown. + Optional transform(S Function(T value) transformer) { + return _value == null + ? Optional.absent() + : Optional.of(transformer(_value as T)); + } + + /// Transforms the Optional value. + /// + /// If the Optional is [absent()], returns [absent()] without applying the transformer. + /// + /// Returns [absent()] if the transformer returns `null`. + Optional transformNullable(S? Function(T value) transformer) { + return _value == null + ? Optional.absent() + : Optional.fromNullable(transformer(_value as T)); + } + + @override + Iterator get iterator => + isPresent ? [_value as T].iterator : Iterable.empty().iterator; + + /// Delegates to the underlying [value] hashCode. + @override + int get hashCode => _value.hashCode; + + /// Delegates to the underlying [value] operator==. + @override + bool operator ==(Object o) => o is Optional && o._value == _value; + + @override + String toString() { + return _value == null + ? 'Optional { absent }' + : 'Optional { value: $_value }'; + } +} diff --git a/packages/camera/camera_android/pubspec.yaml b/packages/camera/camera_android/pubspec.yaml index 30d6153cece6..fed2d29fb59f 100644 --- a/packages/camera/camera_android/pubspec.yaml +++ b/packages/camera/camera_android/pubspec.yaml @@ -2,7 +2,7 @@ name: camera_android description: Android implementation of the camera plugin. repository: https://github.com/flutter/plugins/tree/main/packages/camera/camera_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22 -version: 0.10.2+3 +version: 0.10.3 environment: sdk: ">=2.14.0 <3.0.0" diff --git a/packages/camera/camera_avfoundation/CHANGELOG.md b/packages/camera/camera_avfoundation/CHANGELOG.md index caa69b6296b8..f0605b7914cc 100644 --- a/packages/camera/camera_avfoundation/CHANGELOG.md +++ b/packages/camera/camera_avfoundation/CHANGELOG.md @@ -1,5 +1,6 @@ -## NEXT +## 0.9.11 +* Adds back use of Optional type. * Updates minimum Flutter version to 3.0. ## 0.9.10+2 diff --git a/packages/camera/camera_avfoundation/example/lib/camera_controller.dart b/packages/camera/camera_avfoundation/example/lib/camera_controller.dart index 47c1f6f0415b..524186816aab 100644 --- a/packages/camera/camera_avfoundation/example/lib/camera_controller.dart +++ b/packages/camera/camera_avfoundation/example/lib/camera_controller.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'dart:async'; +import 'dart:collection'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:flutter/foundation.dart'; @@ -108,10 +109,10 @@ class CameraValue { bool? exposurePointSupported, bool? focusPointSupported, DeviceOrientation? deviceOrientation, - DeviceOrientation? lockedCaptureOrientation, - DeviceOrientation? recordingOrientation, + Optional? lockedCaptureOrientation, + Optional? recordingOrientation, bool? isPreviewPaused, - DeviceOrientation? previewPauseOrientation, + Optional? previewPauseOrientation, }) { return CameraValue( isInitialized: isInitialized ?? this.isInitialized, @@ -124,12 +125,16 @@ class CameraValue { exposureMode: exposureMode ?? this.exposureMode, focusMode: focusMode ?? this.focusMode, deviceOrientation: deviceOrientation ?? this.deviceOrientation, - lockedCaptureOrientation: - lockedCaptureOrientation ?? this.lockedCaptureOrientation, - recordingOrientation: recordingOrientation ?? this.recordingOrientation, + lockedCaptureOrientation: lockedCaptureOrientation == null + ? this.lockedCaptureOrientation + : lockedCaptureOrientation.orNull, + recordingOrientation: recordingOrientation == null + ? this.recordingOrientation + : recordingOrientation.orNull, isPreviewPaused: isPreviewPaused ?? this.isPreviewPaused, - previewPauseOrientation: - previewPauseOrientation ?? this.previewPauseOrientation, + previewPauseOrientation: previewPauseOrientation == null + ? this.previewPauseOrientation + : previewPauseOrientation.orNull, ); } @@ -257,14 +262,16 @@ class CameraController extends ValueNotifier { await CameraPlatform.instance.pausePreview(_cameraId); value = value.copyWith( isPreviewPaused: true, - previewPauseOrientation: - value.lockedCaptureOrientation ?? value.deviceOrientation); + previewPauseOrientation: Optional.of( + value.lockedCaptureOrientation ?? value.deviceOrientation)); } /// Resumes the current camera preview Future resumePreview() async { await CameraPlatform.instance.resumePreview(_cameraId); - value = value.copyWith(isPreviewPaused: false); + value = value.copyWith( + isPreviewPaused: false, + previewPauseOrientation: const Optional.absent()); } /// Captures an image and returns the file where it was saved. @@ -307,8 +314,8 @@ class CameraController extends ValueNotifier { isRecordingVideo: true, isRecordingPaused: false, isStreamingImages: streamCallback != null, - recordingOrientation: - value.lockedCaptureOrientation ?? value.deviceOrientation); + recordingOrientation: Optional.of( + value.lockedCaptureOrientation ?? value.deviceOrientation)); } /// Stops the video recording and returns the file where it was saved. @@ -321,7 +328,10 @@ class CameraController extends ValueNotifier { final XFile file = await CameraPlatform.instance.stopVideoRecording(_cameraId); - value = value.copyWith(isRecordingVideo: false); + value = value.copyWith( + isRecordingVideo: false, + recordingOrientation: const Optional.absent(), + ); return file; } @@ -389,12 +399,16 @@ class CameraController extends ValueNotifier { Future lockCaptureOrientation() async { await CameraPlatform.instance .lockCaptureOrientation(_cameraId, value.deviceOrientation); - value = value.copyWith(lockedCaptureOrientation: value.deviceOrientation); + value = value.copyWith( + lockedCaptureOrientation: + Optional.of(value.deviceOrientation)); } /// Unlocks the capture orientation. Future unlockCaptureOrientation() async { await CameraPlatform.instance.unlockCaptureOrientation(_cameraId); + value = value.copyWith( + lockedCaptureOrientation: const Optional.absent()); } /// Sets the focus mode for taking pictures. @@ -428,3 +442,112 @@ class CameraController extends ValueNotifier { } } } + +/// A value that might be absent. +/// +/// Used to represent [DeviceOrientation]s that are optional but also able +/// to be cleared. +@immutable +class Optional extends IterableBase { + /// Constructs an empty Optional. + const Optional.absent() : _value = null; + + /// Constructs an Optional of the given [value]. + /// + /// Throws [ArgumentError] if [value] is null. + Optional.of(T value) : _value = value { + // TODO(cbracken): Delete and make this ctor const once mixed-mode + // execution is no longer around. + ArgumentError.checkNotNull(value); + } + + /// Constructs an Optional of the given [value]. + /// + /// If [value] is null, returns [absent()]. + const Optional.fromNullable(T? value) : _value = value; + + final T? _value; + + /// True when this optional contains a value. + bool get isPresent => _value != null; + + /// True when this optional contains no value. + bool get isNotPresent => _value == null; + + /// Gets the Optional value. + /// + /// Throws [StateError] if [value] is null. + T get value { + if (_value == null) { + throw StateError('value called on absent Optional.'); + } + return _value!; + } + + /// Executes a function if the Optional value is present. + void ifPresent(void Function(T value) ifPresent) { + if (isPresent) { + ifPresent(_value as T); + } + } + + /// Execution a function if the Optional value is absent. + void ifAbsent(void Function() ifAbsent) { + if (!isPresent) { + ifAbsent(); + } + } + + /// Gets the Optional value with a default. + /// + /// The default is returned if the Optional is [absent()]. + /// + /// Throws [ArgumentError] if [defaultValue] is null. + T or(T defaultValue) { + return _value ?? defaultValue; + } + + /// Gets the Optional value, or `null` if there is none. + T? get orNull => _value; + + /// Transforms the Optional value. + /// + /// If the Optional is [absent()], returns [absent()] without applying the transformer. + /// + /// The transformer must not return `null`. If it does, an [ArgumentError] is thrown. + Optional transform(S Function(T value) transformer) { + return _value == null + ? Optional.absent() + : Optional.of(transformer(_value as T)); + } + + /// Transforms the Optional value. + /// + /// If the Optional is [absent()], returns [absent()] without applying the transformer. + /// + /// Returns [absent()] if the transformer returns `null`. + Optional transformNullable(S? Function(T value) transformer) { + return _value == null + ? Optional.absent() + : Optional.fromNullable(transformer(_value as T)); + } + + @override + Iterator get iterator => + isPresent ? [_value as T].iterator : Iterable.empty().iterator; + + /// Delegates to the underlying [value] hashCode. + @override + int get hashCode => _value.hashCode; + + /// Delegates to the underlying [value] operator==. + @override + bool operator ==(Object o) => o is Optional && o._value == _value; + + @override + String toString() { + return _value == null + ? 'Optional { absent }' + : 'Optional { value: $_value }'; + } +} diff --git a/packages/camera/camera_avfoundation/pubspec.yaml b/packages/camera/camera_avfoundation/pubspec.yaml index 9461d934a81d..b272a4c5c68d 100644 --- a/packages/camera/camera_avfoundation/pubspec.yaml +++ b/packages/camera/camera_avfoundation/pubspec.yaml @@ -2,7 +2,7 @@ name: camera_avfoundation description: iOS implementation of the camera plugin. repository: https://github.com/flutter/plugins/tree/main/packages/camera/camera_avfoundation issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22 -version: 0.9.10+2 +version: 0.9.11 environment: sdk: ">=2.14.0 <3.0.0" From 90f447313fb7b3069de215c2c8c9b1ead3ad53f9 Mon Sep 17 00:00:00 2001 From: stuartmorgan Date: Fri, 27 Jan 2023 09:47:09 -0800 Subject: [PATCH 041/130] [ci] Increase timeouts for platform_tests (#7036) `platform_tests` are our most time-consuming tests; 30 minutes isn't always enough to run them. Increase timeouts to 60 minutes so that we aren't getting timeouts from tests that are still running. Also disables Windows `platform_tests` for stable in presubmit, matching other platforms. --- .ci.yaml | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/.ci.yaml b/.ci.yaml index f88c794712ba..231aae553a0a 100644 --- a/.ci.yaml +++ b/.ci.yaml @@ -79,7 +79,7 @@ targets: # https://github.com/flutter/plugins/pull/5693#issuecomment-1126011089 - name: Mac_x64 ios_platform_tests_1_of_4 master recipe: plugins/plugins - timeout: 30 + timeout: 60 properties: add_recipes_cq: "true" version_file: flutter_master.version @@ -88,7 +88,7 @@ targets: - name: Mac_x64 ios_platform_tests_2_of_4 master recipe: plugins/plugins - timeout: 30 + timeout: 60 properties: add_recipes_cq: "true" version_file: flutter_master.version @@ -97,7 +97,7 @@ targets: - name: Mac_x64 ios_platform_tests_3_of_4 master recipe: plugins/plugins - timeout: 30 + timeout: 60 properties: add_recipes_cq: "true" version_file: flutter_master.version @@ -106,7 +106,7 @@ targets: - name: Mac_x64 ios_platform_tests_4_of_4 master recipe: plugins/plugins - timeout: 30 + timeout: 60 properties: add_recipes_cq: "true" version_file: flutter_master.version @@ -117,7 +117,7 @@ targets: - name: Mac_x64 ios_platform_tests_1_of_4 stable recipe: plugins/plugins presubmit: false - timeout: 30 + timeout: 60 properties: channel: stable add_recipes_cq: "true" @@ -128,7 +128,7 @@ targets: - name: Mac_x64 ios_platform_tests_2_of_4 stable recipe: plugins/plugins presubmit: false - timeout: 30 + timeout: 60 properties: channel: stable add_recipes_cq: "true" @@ -139,7 +139,7 @@ targets: - name: Mac_x64 ios_platform_tests_3_of_4 stable recipe: plugins/plugins presubmit: false - timeout: 30 + timeout: 60 properties: channel: stable add_recipes_cq: "true" @@ -150,7 +150,7 @@ targets: - name: Mac_x64 ios_platform_tests_4_of_4 stable recipe: plugins/plugins presubmit: false - timeout: 30 + timeout: 60 properties: channel: stable add_recipes_cq: "true" @@ -160,7 +160,7 @@ targets: - name: Windows win32-platform_tests master recipe: plugins/plugins - timeout: 30 + timeout: 60 properties: add_recipes_cq: "true" target_file: windows_build_and_platform_tests.yaml @@ -173,7 +173,8 @@ targets: - name: Windows win32-platform_tests stable recipe: plugins/plugins - timeout: 30 + presubmit: false + timeout: 60 properties: add_recipes_cq: "true" target_file: windows_build_and_platform_tests.yaml From f5568e4b150b04412324fc1af37205b73a280c61 Mon Sep 17 00:00:00 2001 From: Milvintsiss <38794405+Milvintsiss@users.noreply.github.com> Date: Sat, 28 Jan 2023 16:26:23 +0100 Subject: [PATCH 042/130] [google_sign_in] Add doc for iOS auth with SERVER_CLIENT_ID (#4747) * Add doc for iOS auth with SERVER_CLIENT_ID * Follow pub versioning guidelines * Remove incorrect documentation saying that `clientId` is only configurable on web. * Revert "Add doc for iOS auth with SERVER_CLIENT_ID" This reverts commit 477bd85693b4e546d97af46f01bd84a68d5876f0. * Adds documentation for iOS auth with SERVER_CLIENT_ID --------- Co-authored-by: stuartmorgan --- .../google_sign_in/google_sign_in/CHANGELOG.md | 3 ++- packages/google_sign_in/google_sign_in/README.md | 16 +++++++++++----- .../google_sign_in/google_sign_in/pubspec.yaml | 3 +-- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/packages/google_sign_in/google_sign_in/CHANGELOG.md b/packages/google_sign_in/google_sign_in/CHANGELOG.md index 75769d4dbaa0..8888253313ba 100644 --- a/packages/google_sign_in/google_sign_in/CHANGELOG.md +++ b/packages/google_sign_in/google_sign_in/CHANGELOG.md @@ -1,5 +1,6 @@ -## NEXT +## 5.4.4 +* Adds documentation for iOS auth with SERVER_CLIENT_ID * Updates minimum Flutter version to 3.0. ## 5.4.3 diff --git a/packages/google_sign_in/google_sign_in/README.md b/packages/google_sign_in/google_sign_in/README.md index e467ca8541b9..6961bc67b7df 100644 --- a/packages/google_sign_in/google_sign_in/README.md +++ b/packages/google_sign_in/google_sign_in/README.md @@ -43,7 +43,13 @@ This plugin requires iOS 9.0 or higher. 5. Select `GoogleService-Info.plist` from the file manager. 6. A dialog will show up and ask you to select the targets, select the `Runner` target. -7. Then add the `CFBundleURLTypes` attributes below into the +7. If you need to authenticate to a backend server you can add a + `SERVER_CLIENT_ID` key value pair in your `GoogleService-Info.plist`. + ```xml + SERVER_CLIENT_ID + [YOUR SERVER CLIENT ID] + ``` +8. Then add the `CFBundleURLTypes` attributes below into the `[my_project]/ios/Runner/Info.plist` file. ```xml @@ -65,9 +71,9 @@ This plugin requires iOS 9.0 or higher. ``` -As an alternative to adding `GoogleService-Info.plist` to your Xcode project, you can instead -configure your app in Dart code. In this case, skip steps 3-6 and pass `clientId` and -`serverClientId` to the `GoogleSignIn` constructor: +As an alternative to adding `GoogleService-Info.plist` to your Xcode project, +you can instead configure your app in Dart code. In this case, skip steps 3 to 7 + and pass `clientId` and `serverClientId` to the `GoogleSignIn` constructor: ```dart GoogleSignIn _googleSignIn = GoogleSignIn( @@ -79,7 +85,7 @@ GoogleSignIn _googleSignIn = GoogleSignIn( ); ``` -Note that step 7 is still required. +Note that step 8 is still required. #### iOS additional requirement diff --git a/packages/google_sign_in/google_sign_in/pubspec.yaml b/packages/google_sign_in/google_sign_in/pubspec.yaml index d4f872f584d2..056700284075 100644 --- a/packages/google_sign_in/google_sign_in/pubspec.yaml +++ b/packages/google_sign_in/google_sign_in/pubspec.yaml @@ -3,8 +3,7 @@ description: Flutter plugin for Google Sign-In, a secure authentication system for signing in with a Google account on Android and iOS. repository: https://github.com/flutter/plugins/tree/main/packages/google_sign_in/google_sign_in issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+google_sign_in%22 -version: 5.4.3 - +version: 5.4.4 environment: sdk: ">=2.14.0 <3.0.0" From 0c05e8d9109bea5bcc07f82262622eb91ec42c61 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Sat, 28 Jan 2023 15:02:08 -0500 Subject: [PATCH 043/130] Roll Flutter from a815ee634202 to 75680ae99e85 (58 revisions) (#7048) * a0f7c8cf7 6f806491e [web] use a render target instead of a new surface for Picture.toImage (flutter/engine#38573) (flutter/flutter#119143) * e85547b3c Roll Plugins from 11361d01099d to 8bab180a668a (28 revisions) (flutter/flutter#119115) * 81052a7d3 Add usage event to track when a iOS network device is used (flutter/flutter#118915) * cd34fa6d4 24aa324b8 Roll Skia from da5034f9d117 to c4b171fe5668 (1 revision) (flutter/engine#39127) (flutter/flutter#119159) * 6cd4fa45e Add --serve-observatory flag to run, attach, and test (flutter/flutter#118402) * 48cd95dd1 1e5efd144 [various] Enable use_build_context_synchronously (flutter/plugins#6585) (flutter/flutter#119162) * b907acdde Add the cupertino system colors mint, cyan, and brown (flutter/flutter#118971) * c6fa5d957 c54580138 Only build analyze_snapshot on Linux host (flutter/engine#39129) (flutter/flutter#119164) * f34ce86cf 7b72038ef Roll Fuchsia Linux SDK from E9m-Gk382PkB7_Nbp... to pGX7tanT1okL8XCg-... (flutter/engine#39130) (flutter/flutter#119169) * 0dd63d331 Export View (flutter/flutter#117475) * 1fd71de0c Remove superfluous words from comments (flutter/flutter#119055) * cef9cc717 2e7d6fa7b Remove unnecessary null checks (flutter/engine#39113) (flutter/flutter#119174) * 3be330aaf 30c02e4c8 [Impeller] Make text glyph offsets respect the current transform (flutter/engine#39119) (flutter/flutter#119179) * a45727d81 Add MediaQuery to View (flutter/flutter#118004) * 02a9c151f Fix lexer issue where select/plural/other/underscores cannot be in identifier names. (flutter/flutter#119190) * 766e4d28a Remove single-view assumption from material library (flutter/flutter#117486) * dcd367951 Roll Flutter Engine from 30c02e4c8b01 to 44362c90fcec (2 revisions) (flutter/flutter#119185) * 9037e3fe2 roll packages (flutter/flutter#119192) * e0e88da15 Roll Flutter Engine from 44362c90fcec to 308ce918f67f (2 revisions) (flutter/flutter#119201) * 202e90274 Roll Flutter Engine from 308ce918f67f to 8f1e5dc1b124 (4 revisions) (flutter/flutter#119208) * b319938ec Add more flexible image API (flutter/flutter#118966) * fc0270181 Marks Mac run_debug_test_macos to be unflaky (flutter/flutter#117470) * c9affdba9 Move windows-x64-flutter.zip to windows-x64-debug location. (flutter/flutter#119177) * 7d3b762df Fix: Added `margin` parameter for `MaterialBanner` class (flutter/flutter#119005) * 40bd82ef6 Roll Plugins from 1e5efd144f93 to e9406bc209a2 (4 revisions) (flutter/flutter#119249) * 07522b74e Roll Flutter Engine from 8f1e5dc1b124 to 04f22beebb42 (5 revisions) (flutter/flutter#119218) * 459c1b78b Marks Mac complex_layout_scroll_perf_macos__timeline_summary to be unflaky (flutter/flutter#119157) * 2b8f2d050 Add API for discovering assets (flutter/flutter#118410) * a04ab7129 Revert "Add API for discovering assets (#118410)" (flutter/flutter#119273) * 1da487dfb Roll Flutter Engine from 04f22beebb42 to 93901260098e (12 revisions) (flutter/flutter#119279) * 1b779b655 Roll Flutter Engine from 93901260098e to be0125bd5716 (2 revisions) (flutter/flutter#119283) * 42bd5f2bd Download platform-agnostic Flutter Web SDK in the flutter_tool (flutter/flutter#118654) * d52b6b989 Roll Flutter Engine from be0125bd5716 to d17004dd96d7 (2 revisions) (flutter/flutter#119287) * 4aed487ca Roll Flutter Engine from d17004dd96d7 to a63d98feb608 (3 revisions) (flutter/flutter#119299) * 05fc29fe7 Rename DeviceGestureSettings.fromWindow to DeviceGestureSettings.fromView (flutter/flutter#119291) * 86ab01d2b Revert "Add --serve-observatory flag to run, attach, and test (#118402)" (flutter/flutter#119302) * 8d03af342 Roll Flutter Engine from a63d98feb608 to 79c958fc7e9b (3 revisions) (flutter/flutter#119306) * 27f8ebdae ade610ec8 [fuchsia] Migrate to new RealmBuilder API (flutter/engine#39175) (flutter/flutter#119310) * c31856bc4 Roll Plugins from e9406bc209a2 to ff84c44a5ddb (2 revisions) (flutter/flutter#119335) * d939863a2 Roll Flutter Engine from ade610ec88b5 to 621e13cc9be3 (3 revisions) (flutter/flutter#119344) * 0b5759671 Run "flutter update-packages --force-upgrade" (flutter/flutter#119340) * 0417f6621 Fix nullability of TableRow.children (flutter/flutter#119285) * fc3e8243c Roll Flutter Engine from 621e13cc9be3 to 189a69d9918d (3 revisions) (flutter/flutter#119347) * ad1a44d0a Add `requestFocusOnTap` to `DropdownMenu` (flutter/flutter#117504) * 4dbb573ff [flutter_tools] remove usage of remap samplers arg (flutter/flutter#119346) * b2f2bf31c Marks Linux run_release_test_linux to be unflaky (flutter/flutter#119156) * 3f95befe5 Roll Flutter Engine from 189a69d9918d to b32fc7fef208 (3 revisions) (flutter/flutter#119358) * 2e8bebd17 Remove single window assumption from macrobenchmark (flutter/flutter#119368) * ab2232a18 Roll Flutter Engine from b32fc7fef208 to 8567d96993ed (5 revisions) (flutter/flutter#119369) * e9ca9cc14 Remove references to dart:ui's window singelton (flutter/flutter#119296) * da3d4bd0e Roll Flutter Engine from 8567d96993ed to 225ae87334a5 (2 revisions) (flutter/flutter#119376) * 018c1f84a e2e089ebb Use arm64 engine variant on simulators in iOS unit tests (flutter/engine#39213) (flutter/flutter#119387) * e349bdc1a 19651cb1d Roll Dart SDK from 2cd9b7ac95e8 to 135f4c51c9ff (3 revisions) (flutter/engine#39214) (flutter/flutter#119389) * 95345b516 77bee011d Roll Dart SDK from 2cd9b7ac95e8 to 135f4c51c9ff (3 revisions) (flutter/engine#39217) (flutter/flutter#119394) * de43ec977 Roll Flutter Engine from 77bee011dabf to 3394b84cc5d7 (3 revisions) (flutter/flutter#119405) * f8d4de488 3dd0fc13f Roll Fuchsia Linux SDK from 6c2H32X3EXOGlWIgb... to TiK_fVODtUaKOgxRf... (flutter/engine#39224) (flutter/flutter#119408) * 785641160 7c5c6c9c9 Roll Skia from 0b75650caf2a to 7df7a83f733d (13 revisions) (flutter/engine#39225) (flutter/flutter#119413) * 75680ae99 649362168 Roll Dart SDK from f9583e13e214 to 52dc94238144 (1 revision) (flutter/engine#39227) (flutter/flutter#119416) --- .ci/flutter_master.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/flutter_master.version b/.ci/flutter_master.version index 1555264bb14a..17714f3e5953 100644 --- a/.ci/flutter_master.version +++ b/.ci/flutter_master.version @@ -1 +1 @@ -a815ee634202c0a9e1be9c73450226c7cff97cff +75680ae99e858a075778571ca01ce1668ba2b819 From a4c320902ee6b14c556c5c66945f599cf787f952 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Jan 2023 18:27:18 +0000 Subject: [PATCH 044/130] [camera]: Bump camerax_version from 1.3.0-alpha02 to 1.3.0-alpha03 in /packages/camera/camera_android_camerax/android (#7061) * [camera]: Bump camerax_version Bumps `camerax_version` from 1.3.0-alpha02 to 1.3.0-alpha03. Updates `camera-core` from 1.3.0-alpha02 to 1.3.0-alpha03 Updates `camera-camera2` from 1.3.0-alpha02 to 1.3.0-alpha03 Updates `camera-lifecycle` from 1.3.0-alpha02 to 1.3.0-alpha03 --- updated-dependencies: - dependency-name: androidx.camera:camera-core dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: androidx.camera:camera-camera2 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: androidx.camera:camera-lifecycle dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * Update changelog * Bump Kotlin version --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: camsim99 --- packages/camera/camera_android_camerax/CHANGELOG.md | 1 + packages/camera/camera_android_camerax/android/build.gradle | 2 +- .../camera/camera_android_camerax/example/android/build.gradle | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/camera/camera_android_camerax/CHANGELOG.md b/packages/camera/camera_android_camerax/CHANGELOG.md index 389fc31e26e7..e248623ef678 100644 --- a/packages/camera/camera_android_camerax/CHANGELOG.md +++ b/packages/camera/camera_android_camerax/CHANGELOG.md @@ -6,3 +6,4 @@ * Adds ProcessCameraProvider class. * Bump CameraX version to 1.3.0-alpha02. * Adds Camera and UseCase classes, along with methods for binding UseCases to a lifecycle with the ProcessCameraProvider. +* Bump CameraX version to 1.3.0-alpha03 and Kotlin version to 1.8.0. diff --git a/packages/camera/camera_android_camerax/android/build.gradle b/packages/camera/camera_android_camerax/android/build.gradle index 1e04ad101670..822c3f6e318e 100644 --- a/packages/camera/camera_android_camerax/android/build.gradle +++ b/packages/camera/camera_android_camerax/android/build.gradle @@ -56,7 +56,7 @@ android { dependencies { // CameraX core library using the camera2 implementation must use same version number. - def camerax_version = "1.3.0-alpha02" + def camerax_version = "1.3.0-alpha03" implementation "androidx.camera:camera-core:${camerax_version}" implementation "androidx.camera:camera-camera2:${camerax_version}" implementation "androidx.camera:camera-lifecycle:${camerax_version}" diff --git a/packages/camera/camera_android_camerax/example/android/build.gradle b/packages/camera/camera_android_camerax/example/android/build.gradle index 20411f5f31a9..8640e4de86a1 100644 --- a/packages/camera/camera_android_camerax/example/android/build.gradle +++ b/packages/camera/camera_android_camerax/example/android/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.7.10' + ext.kotlin_version = '1.8.0' repositories { google() mavenCentral() From 8f12b27b687d45b53aa8e9253bcfcad743fd8fa0 Mon Sep 17 00:00:00 2001 From: stuartmorgan Date: Mon, 30 Jan 2023 11:17:48 -0800 Subject: [PATCH 045/130] [ci] Add LUCI versions of macOS ARM tests (#6984) Adds a macOS arm64 configuration, and adds LUCI versions of the remaining Cirrus macOS-host tests (iOS build-all and macOS platform tests) in bringup mode, to begin testing a LUCI migration for the ARM tests in this repository. --- .ci.yaml | 85 +++++++++++++++---- .ci/scripts/build_all_plugins.sh | 3 +- .ci/targets/ios_build_all_plugins.yaml | 11 +++ ...orm_tests.yaml => ios_platform_tests.yaml} | 0 .ci/targets/mac_build_all_plugins.yaml | 4 +- .ci/targets/mac_platform_tests.yaml | 19 +++++ ...ns.yaml => windows_build_all_plugins.yaml} | 4 +- 7 files changed, 106 insertions(+), 20 deletions(-) create mode 100644 .ci/targets/ios_build_all_plugins.yaml rename .ci/targets/{mac_ios_platform_tests.yaml => ios_platform_tests.yaml} (100%) create mode 100644 .ci/targets/mac_platform_tests.yaml rename .ci/targets/{build_all_plugins.yaml => windows_build_all_plugins.yaml} (76%) diff --git a/.ci.yaml b/.ci.yaml index 231aae553a0a..b99ed5460056 100644 --- a/.ci.yaml +++ b/.ci.yaml @@ -25,6 +25,17 @@ platform_properties: ] device_type: none os: Windows + mac_arm64: + properties: + dependencies: >- + [ + {"dependency": "xcode", "version": "14a5294e"}, + {"dependency": "gems", "version": "v3.3.14"} + ] + os: Mac-12 + device_type: none + cpu: arm64 + xcode: 14a5294e # xcode 14.0 beta 5 mac_x64: properties: dependencies: >- @@ -36,7 +47,7 @@ platform_properties: device_type: none cpu: x86 xcode: 14a5294e # xcode 14.0 beta 5 - + targets: ### iOS+macOS tasks *** @@ -53,7 +64,7 @@ targets: target_file: mac_lint_podspecs.yaml ### macOS desktop tasks ### - # macos-platform_tests builds all the plugins on M1, so this build is run + # macos-platform_tests builds all the plugins on ARM, so this build is run # on Intel to give us build coverage of both host types. - name: Mac_x64 build_all_plugins master recipe: plugins/plugins @@ -73,17 +84,61 @@ targets: target_file: mac_build_all_plugins.yaml channel: stable + - name: Mac_arm64 macos_platform_tests master + bringup: true # New task + recipe: plugins/plugins + timeout: 60 + properties: + channel: master + add_recipes_cq: "true" + version_file: flutter_master.version + target_file: macos_platform_tests.yaml + + - name: Mac_arm64 macos_platform_tests stable + bringup: true # New task + recipe: plugins/plugins + presubmit: false + timeout: 60 + properties: + channel: stable + add_recipes_cq: "true" + version_file: flutter_stable.version + target_file: macos_platform_tests.yaml + ### iOS tasks ### - # TODO(stuartmorgan): Swap this and ios-build_all_plugins once simulator - # tests are reliable on the ARM infrastructure. See discussion at - # https://github.com/flutter/plugins/pull/5693#issuecomment-1126011089 + # TODO(stuartmorgan): Swap the architecture of this and ios_platform_tests_* + # once simulator tests are reliable on the ARM infrastructure. See discussion + # at https://github.com/flutter/plugins/pull/5693#issuecomment-1126011089 + - name: Mac_arm64 ios_build_all_plugins master + bringup: true # New task + recipe: plugins/plugins + timeout: 30 + properties: + channel: master + add_recipes_cq: "true" + version_file: flutter_master.version + target_file: ios_build_all_plugins.yaml + + - name: Mac_arm64 ios_build_all_plugins stable + bringup: true # New task + recipe: plugins/plugins + timeout: 30 + properties: + channel: stable + add_recipes_cq: "true" + version_file: flutter_stable.version + target_file: ios_build_all_plugins.yaml + + # TODO(stuartmorgan): Swap the architecture of this and ios_build_all_plugins + # once simulator tests are reliable on the ARM infrastructure. See discussion + # at https://github.com/flutter/plugins/pull/5693#issuecomment-1126011089 - name: Mac_x64 ios_platform_tests_1_of_4 master recipe: plugins/plugins timeout: 60 properties: add_recipes_cq: "true" version_file: flutter_master.version - target_file: mac_ios_platform_tests.yaml + target_file: ios_platform_tests.yaml package_sharding: "--shardIndex 0 --shardCount 4" - name: Mac_x64 ios_platform_tests_2_of_4 master @@ -92,7 +147,7 @@ targets: properties: add_recipes_cq: "true" version_file: flutter_master.version - target_file: mac_ios_platform_tests.yaml + target_file: ios_platform_tests.yaml package_sharding: "--shardIndex 1 --shardCount 4" - name: Mac_x64 ios_platform_tests_3_of_4 master @@ -101,7 +156,7 @@ targets: properties: add_recipes_cq: "true" version_file: flutter_master.version - target_file: mac_ios_platform_tests.yaml + target_file: ios_platform_tests.yaml package_sharding: "--shardIndex 2 --shardCount 4" - name: Mac_x64 ios_platform_tests_4_of_4 master @@ -110,7 +165,7 @@ targets: properties: add_recipes_cq: "true" version_file: flutter_master.version - target_file: mac_ios_platform_tests.yaml + target_file: ios_platform_tests.yaml package_sharding: "--shardIndex 3 --shardCount 4" # Don't run full platform tests on both channels in pre-submit. @@ -122,7 +177,7 @@ targets: channel: stable add_recipes_cq: "true" version_file: flutter_stable.version - target_file: mac_ios_platform_tests.yaml + target_file: ios_platform_tests.yaml package_sharding: "--shardIndex 0 --shardCount 4" - name: Mac_x64 ios_platform_tests_2_of_4 stable @@ -133,7 +188,7 @@ targets: channel: stable add_recipes_cq: "true" version_file: flutter_stable.version - target_file: mac_ios_platform_tests.yaml + target_file: ios_platform_tests.yaml package_sharding: "--shardIndex 1 --shardCount 4" - name: Mac_x64 ios_platform_tests_3_of_4 stable @@ -144,7 +199,7 @@ targets: channel: stable add_recipes_cq: "true" version_file: flutter_stable.version - target_file: mac_ios_platform_tests.yaml + target_file: ios_platform_tests.yaml package_sharding: "--shardIndex 2 --shardCount 4" - name: Mac_x64 ios_platform_tests_4_of_4 stable @@ -155,7 +210,7 @@ targets: channel: stable add_recipes_cq: "true" version_file: flutter_stable.version - target_file: mac_ios_platform_tests.yaml + target_file: ios_platform_tests.yaml package_sharding: "--shardIndex 3 --shardCount 4" - name: Windows win32-platform_tests master @@ -190,7 +245,7 @@ targets: timeout: 30 properties: add_recipes_cq: "true" - target_file: build_all_plugins.yaml + target_file: windows_build_all_plugins.yaml channel: master version_file: flutter_master.version dependencies: > @@ -203,7 +258,7 @@ targets: timeout: 30 properties: add_recipes_cq: "true" - target_file: build_all_plugins.yaml + target_file: windows_build_all_plugins.yaml channel: stable version_file: flutter_stable.version dependencies: > diff --git a/.ci/scripts/build_all_plugins.sh b/.ci/scripts/build_all_plugins.sh index 89dab629fd52..c22b9832ff22 100644 --- a/.ci/scripts/build_all_plugins.sh +++ b/.ci/scripts/build_all_plugins.sh @@ -5,5 +5,6 @@ platform="$1" build_mode="$2" +shift 2 cd all_packages -flutter build "$platform" --"$build_mode" +flutter build "$platform" --"$build_mode" "$@" diff --git a/.ci/targets/ios_build_all_plugins.yaml b/.ci/targets/ios_build_all_plugins.yaml new file mode 100644 index 000000000000..7b5b88d9c9ff --- /dev/null +++ b/.ci/targets/ios_build_all_plugins.yaml @@ -0,0 +1,11 @@ +tasks: + - name: prepare tool + script: .ci/scripts/prepare_tool.sh + - name: create all_plugins app + script: .ci/scripts/create_all_plugins_app.sh + - name: build all_plugins for iOS debug + script: .ci/scripts/build_all_plugins.sh + args: ["ios", "debug", "--no-codesign"] + - name: build all_plugins for iOS release + script: .ci/scripts/build_all_plugins.sh + args: ["ios", "release", "--no-codesign"] diff --git a/.ci/targets/mac_ios_platform_tests.yaml b/.ci/targets/ios_platform_tests.yaml similarity index 100% rename from .ci/targets/mac_ios_platform_tests.yaml rename to .ci/targets/ios_platform_tests.yaml diff --git a/.ci/targets/mac_build_all_plugins.yaml b/.ci/targets/mac_build_all_plugins.yaml index 4dd324e8b3f0..e6eb8ac2c315 100644 --- a/.ci/targets/mac_build_all_plugins.yaml +++ b/.ci/targets/mac_build_all_plugins.yaml @@ -3,9 +3,9 @@ tasks: script: .ci/scripts/prepare_tool.sh - name: create all_plugins app script: .ci/scripts/create_all_plugins_app.sh - - name: build all_plugins debug + - name: build all_plugins for macOS debug script: .ci/scripts/build_all_plugins.sh args: ["macos", "debug"] - - name: build all_plugins release + - name: build all_plugins for macOS release script: .ci/scripts/build_all_plugins.sh args: ["macos", "release"] diff --git a/.ci/targets/mac_platform_tests.yaml b/.ci/targets/mac_platform_tests.yaml new file mode 100644 index 000000000000..4b2ee4eac1fe --- /dev/null +++ b/.ci/targets/mac_platform_tests.yaml @@ -0,0 +1,19 @@ +tasks: + - name: prepare tool + script: .ci/scripts/prepare_tool.sh + - name: build examples + script: script/tool_runner.sh + args: ["build-examples", "--macos"] + - name: xcode analyze + script: script/tool_runner.sh + args: ["xcode-analyze", "--macos"] + - name: xcode analyze deprecation + # Ensure we don't accidentally introduce deprecated code. + script: script/tool_runner.sh + args: ["xcode-analyze", "--macos", "--macos-min-version=12.3"] + - name: native test + script: script/tool_runner.sh + args: ["native-test", "--macos"] + - name: drive examples + script: script/tool_runner.sh + args: ["drive-examples", "--macos", "--exclude=script/configs/exclude_integration_macos.yaml"] diff --git a/.ci/targets/build_all_plugins.yaml b/.ci/targets/windows_build_all_plugins.yaml similarity index 76% rename from .ci/targets/build_all_plugins.yaml rename to .ci/targets/windows_build_all_plugins.yaml index 0ffbdfcce376..53d6b99e2444 100644 --- a/.ci/targets/build_all_plugins.yaml +++ b/.ci/targets/windows_build_all_plugins.yaml @@ -3,9 +3,9 @@ tasks: script: .ci/scripts/prepare_tool.sh - name: create all_plugins app script: .ci/scripts/create_all_plugins_app.sh - - name: build all_plugins debug + - name: build all_plugins for Windows debug script: .ci/scripts/build_all_plugins.sh args: ["windows", "debug"] - - name: build all_plugins release + - name: build all_plugins for Windows release script: .ci/scripts/build_all_plugins.sh args: ["windows", "release"] From 3843b38e2ee8d3f0b1362b46dbb5b5ac7991eefc Mon Sep 17 00:00:00 2001 From: stuartmorgan Date: Mon, 30 Jan 2023 13:32:05 -0800 Subject: [PATCH 046/130] [tool] Improve main-branch detection (#7038) * [tool] Improve main-branch detection Currently main-branch detection for `--packages-for-branch` looks at branch names, but this no longer works on LUCI which now seems to be checking out specific hashes rather than branches. This updates the behavior so that it will treat any hash that is an ancestor of `main` as being part of `main`, which should allow post-submit detection to work under LUCI. Fixes https://github.com/flutter/flutter/issues/119330 * Fix throw * Fix typos * Update comment --- script/tool/CHANGELOG.md | 5 ++ .../tool/lib/src/common/package_command.dart | 34 +++++++++-- script/tool/pubspec.yaml | 2 +- .../test/common/package_command_test.dart | 56 ++++++++++++++++--- 4 files changed, 83 insertions(+), 14 deletions(-) diff --git a/script/tool/CHANGELOG.md b/script/tool/CHANGELOG.md index 34def6ecf676..3c4905ad7071 100644 --- a/script/tool/CHANGELOG.md +++ b/script/tool/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.13.4+1 + +* Makes `--packages-for-branch` detect any commit on `main` as being `main`, + so that it works with pinned checkouts (e.g., on LUCI). + ## 0.13.4 * Adds the ability to validate minimum supported Dart/Flutter versions in diff --git a/script/tool/lib/src/common/package_command.dart b/script/tool/lib/src/common/package_command.dart index 0e83d03e9846..0e084c4c2d5c 100644 --- a/script/tool/lib/src/common/package_command.dart +++ b/script/tool/lib/src/common/package_command.dart @@ -316,17 +316,28 @@ abstract class PackageCommand extends Command { } else if (getBoolArg(_packagesForBranchArg)) { final String? branch = await _getBranch(); if (branch == null) { - printError('Unabled to determine branch; --$_packagesForBranchArg can ' + printError('Unable to determine branch; --$_packagesForBranchArg can ' 'only be used in a git repository.'); throw ToolExit(exitInvalidArguments); } else { // Configure the change finder the correct mode for the branch. - final bool lastCommitOnly = branch == 'main' || branch == 'master'; + // Log the mode to make it easier to audit logs to see that the + // intended diff was used (or why). + final bool lastCommitOnly; + if (branch == 'main' || branch == 'master') { + print('--$_packagesForBranchArg: running on default branch.'); + lastCommitOnly = true; + } else if (await _isCheckoutFromBranch('main')) { + print( + '--$_packagesForBranchArg: running on a commit from default branch.'); + lastCommitOnly = true; + } else { + print('--$_packagesForBranchArg: running on branch "$branch".'); + lastCommitOnly = false; + } if (lastCommitOnly) { - // Log the mode to make it easier to audit logs to see that the - // intended diff was used. - print('--$_packagesForBranchArg: running on default branch; ' - 'using parent commit as the diff base.'); + print( + '--$_packagesForBranchArg: using parent commit as the diff base.'); changedFileFinder = GitVersionFinder(await gitDir, 'HEAD~'); } else { changedFileFinder = await retrieveVersionFinder(); @@ -522,6 +533,17 @@ abstract class PackageCommand extends Command { return packages; } + // Returns true if the current checkout is on an ancestor of [branch]. + // + // This is used because CI may check out a specific hash rather than a branch, + // in which case branch-name detection won't work. + Future _isCheckoutFromBranch(String branchName) async { + final io.ProcessResult result = await (await gitDir).runCommand( + ['merge-base', '--is-ancestor', 'HEAD', branchName], + throwOnError: false); + return result.exitCode == 0; + } + Future _getBranch() async { final io.ProcessResult branchResult = await (await gitDir).runCommand( ['rev-parse', '--abbrev-ref', 'HEAD'], diff --git a/script/tool/pubspec.yaml b/script/tool/pubspec.yaml index a8df2a9cd23a..73429638a402 100644 --- a/script/tool/pubspec.yaml +++ b/script/tool/pubspec.yaml @@ -1,7 +1,7 @@ name: flutter_plugin_tools description: Productivity utils for flutter/plugins and flutter/packages repository: https://github.com/flutter/plugins/tree/main/script/tool -version: 0.13.4 +version: 0.13.4+1 dependencies: args: ^2.1.0 diff --git a/script/tool/test/common/package_command_test.dart b/script/tool/test/common/package_command_test.dart index aa0a20253955..ed76408bb300 100644 --- a/script/tool/test/common/package_command_test.dart +++ b/script/tool/test/common/package_command_test.dart @@ -778,7 +778,8 @@ packages/b_package/lib/src/foo.dart MockProcess(stdout: 'a-branch'), ]; processRunner.mockProcessesForExecutable['git-merge-base'] = [ - MockProcess(stdout: 'abc123'), + MockProcess(exitCode: 1), // --is-ancestor check + MockProcess(stdout: 'abc123'), // finding merge base ]; final RepositoryPackage plugin1 = createFakePlugin('plugin1', packagesDir); @@ -791,6 +792,7 @@ packages/b_package/lib/src/foo.dart expect( output, containsAllInOrder([ + contains('--packages-for-branch: running on branch "a-branch"'), contains( 'Running for all packages that have diffs relative to "abc123"'), ])); @@ -822,8 +824,9 @@ packages/b_package/lib/src/foo.dart expect( output, containsAllInOrder([ - contains('--packages-for-branch: running on default branch; ' - 'using parent commit as the diff base'), + contains('--packages-for-branch: running on default branch.'), + contains( + '--packages-for-branch: using parent commit as the diff base'), contains( 'Running for all packages that have diffs relative to "HEAD~"'), ])); @@ -836,7 +839,45 @@ packages/b_package/lib/src/foo.dart )); }); - test('tests all packages on master', () async { + test( + 'only tests changed packages relative to the previous commit if ' + 'running on a specific hash from main', () async { + processRunner.mockProcessesForExecutable['git-diff'] = [ + MockProcess(stdout: 'packages/plugin1/plugin1.dart'), + ]; + processRunner.mockProcessesForExecutable['git-rev-parse'] = [ + MockProcess(stdout: 'HEAD'), + ]; + final RepositoryPackage plugin1 = + createFakePlugin('plugin1', packagesDir); + createFakePlugin('plugin2', packagesDir); + + final List output = await runCapturingPrint( + runner, ['sample', '--packages-for-branch']); + + expect(command.plugins, unorderedEquals([plugin1.path])); + expect( + output, + containsAllInOrder([ + contains( + '--packages-for-branch: running on a commit from default branch.'), + contains( + '--packages-for-branch: using parent commit as the diff base'), + contains( + 'Running for all packages that have diffs relative to "HEAD~"'), + ])); + // Ensure that it's diffing against the prior commit. + expect( + processRunner.recordedCalls, + contains( + const ProcessCall( + 'git-diff', ['--name-only', 'HEAD~', 'HEAD'], null), + )); + }); + + test( + 'only tests changed packages relative to the previous commit on master', + () async { processRunner.mockProcessesForExecutable['git-diff'] = [ MockProcess(stdout: 'packages/plugin1/plugin1.dart'), ]; @@ -854,8 +895,9 @@ packages/b_package/lib/src/foo.dart expect( output, containsAllInOrder([ - contains('--packages-for-branch: running on default branch; ' - 'using parent commit as the diff base'), + contains('--packages-for-branch: running on default branch.'), + contains( + '--packages-for-branch: using parent commit as the diff base'), contains( 'Running for all packages that have diffs relative to "HEAD~"'), ])); @@ -887,7 +929,7 @@ packages/b_package/lib/src/foo.dart expect( output, containsAllInOrder([ - contains('Unabled to determine branch'), + contains('Unable to determine branch'), ])); }); }); From d39e7569c535200e959eb9dc659ec3ff00db2a80 Mon Sep 17 00:00:00 2001 From: Michael Goderbauer Date: Mon, 30 Jan 2023 16:00:32 -0800 Subject: [PATCH 047/130] [in_app_purchase] Prep for more const widgets (#7030) --- packages/in_app_purchase/in_app_purchase/CHANGELOG.md | 4 ++++ .../in_app_purchase/in_app_purchase/example/lib/main.dart | 2 ++ packages/in_app_purchase/in_app_purchase/pubspec.yaml | 2 +- packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md | 3 ++- .../in_app_purchase_android/example/lib/main.dart | 2 ++ packages/in_app_purchase/in_app_purchase_android/pubspec.yaml | 2 +- .../in_app_purchase/in_app_purchase_storekit/CHANGELOG.md | 3 ++- .../in_app_purchase_storekit/example/lib/main.dart | 2 ++ .../in_app_purchase/in_app_purchase_storekit/pubspec.yaml | 2 +- 9 files changed, 17 insertions(+), 5 deletions(-) diff --git a/packages/in_app_purchase/in_app_purchase/CHANGELOG.md b/packages/in_app_purchase/in_app_purchase/CHANGELOG.md index 38355e35a849..7279685afc5d 100644 --- a/packages/in_app_purchase/in_app_purchase/CHANGELOG.md +++ b/packages/in_app_purchase/in_app_purchase/CHANGELOG.md @@ -1,3 +1,7 @@ +## 3.1.3 + +* Ignores a lint in the example app for backwards compatibility. + ## 3.1.2 * Updates example code for `use_build_context_synchronously` lint. diff --git a/packages/in_app_purchase/in_app_purchase/example/lib/main.dart b/packages/in_app_purchase/in_app_purchase/example/lib/main.dart index aec19fed5272..21268d4e7e8a 100644 --- a/packages/in_app_purchase/in_app_purchase/example/lib/main.dart +++ b/packages/in_app_purchase/in_app_purchase/example/lib/main.dart @@ -164,6 +164,8 @@ class _MyAppState extends State<_MyApp> { } if (_purchasePending) { stack.add( + // TODO(goderbauer): Make this const when that's available on stable. + // ignore: prefer_const_constructors Stack( children: const [ Opacity( diff --git a/packages/in_app_purchase/in_app_purchase/pubspec.yaml b/packages/in_app_purchase/in_app_purchase/pubspec.yaml index 598ab909fd84..443487465a27 100644 --- a/packages/in_app_purchase/in_app_purchase/pubspec.yaml +++ b/packages/in_app_purchase/in_app_purchase/pubspec.yaml @@ -2,7 +2,7 @@ name: in_app_purchase description: A Flutter plugin for in-app purchases. Exposes APIs for making in-app purchases through the App Store and Google Play. repository: https://github.com/flutter/plugins/tree/main/packages/in_app_purchase/in_app_purchase issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+in_app_purchase%22 -version: 3.1.2 +version: 3.1.3 environment: sdk: ">=2.12.0 <3.0.0" diff --git a/packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md b/packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md index 48987c66b09b..4e199f2d03bf 100644 --- a/packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md +++ b/packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md @@ -1,6 +1,7 @@ -## NEXT +## 0.2.4 * Updates minimum Flutter version to 3.0. +* Ignores a lint in the example app for backwards compatibility. ## 0.2.3+9 diff --git a/packages/in_app_purchase/in_app_purchase_android/example/lib/main.dart b/packages/in_app_purchase/in_app_purchase_android/example/lib/main.dart index cc612d1918b6..97e71b038be3 100644 --- a/packages/in_app_purchase/in_app_purchase_android/example/lib/main.dart +++ b/packages/in_app_purchase/in_app_purchase_android/example/lib/main.dart @@ -156,6 +156,8 @@ class _MyAppState extends State<_MyApp> { } if (_purchasePending) { stack.add( + // TODO(goderbauer): Make this const when that's available on stable. + // ignore: prefer_const_constructors Stack( children: const [ Opacity( diff --git a/packages/in_app_purchase/in_app_purchase_android/pubspec.yaml b/packages/in_app_purchase/in_app_purchase_android/pubspec.yaml index 7f36bf31f0bd..e02cb66627cf 100644 --- a/packages/in_app_purchase/in_app_purchase_android/pubspec.yaml +++ b/packages/in_app_purchase/in_app_purchase_android/pubspec.yaml @@ -2,7 +2,7 @@ name: in_app_purchase_android description: An implementation for the Android platform of the Flutter `in_app_purchase` plugin. This uses the Android BillingClient APIs. repository: https://github.com/flutter/plugins/tree/main/packages/in_app_purchase/in_app_purchase_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+in_app_purchase%22 -version: 0.2.3+9 +version: 0.2.4 environment: sdk: ">=2.14.0 <3.0.0" diff --git a/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md b/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md index 6c89b75dcefb..aef5d16a2a25 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md +++ b/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md @@ -1,6 +1,7 @@ -## NEXT +## 0.3.5 * Updates minimum Flutter version to 3.0. +* Ignores a lint in the example app for backwards compatibility. ## 0.3.4+1 diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/lib/main.dart b/packages/in_app_purchase/in_app_purchase_storekit/example/lib/main.dart index 09058ea2e89a..ce06aa1d1ab6 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/lib/main.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/lib/main.dart @@ -156,6 +156,8 @@ class _MyAppState extends State<_MyApp> { } if (_purchasePending) { stack.add( + // TODO(goderbauer): Make this const when that's available on stable. + // ignore: prefer_const_constructors Stack( children: const [ Opacity( diff --git a/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml b/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml index cb0243c4f56a..a4e553ab7c2c 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml +++ b/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml @@ -2,7 +2,7 @@ name: in_app_purchase_storekit description: An implementation for the iOS and macOS platforms of the Flutter `in_app_purchase` plugin. This uses the StoreKit Framework. repository: https://github.com/flutter/plugins/tree/main/packages/in_app_purchase/in_app_purchase_storekit issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+in_app_purchase%22 -version: 0.3.4+1 +version: 0.3.5 environment: sdk: ">=2.14.0 <3.0.0" From ddb9777ee45a83c24251208803b1d7bfd85a2a05 Mon Sep 17 00:00:00 2001 From: stuartmorgan Date: Mon, 30 Jan 2023 16:01:54 -0800 Subject: [PATCH 048/130] [ci] Switch remaining macOS host tests to LUCI (#7063) * [ci] Switch remaining macOS host tests to LUCI Enables the new LUCI versions of the remaining Cirrus macOS host tests, and removes the Cirrus versions. This completes the macOS LUCI transition for flutter/plugins, leaving only Linux on Cirrus. * standardize naming as macos_ --- .ci.yaml | 10 ++-- ...gins.yaml => macos_build_all_plugins.yaml} | 0 ...podspecs.yaml => macos_lint_podspecs.yaml} | 0 ...m_tests.yaml => macos_platform_tests.yaml} | 0 .cirrus.yml | 49 ------------------- 5 files changed, 3 insertions(+), 56 deletions(-) rename .ci/targets/{mac_build_all_plugins.yaml => macos_build_all_plugins.yaml} (100%) rename .ci/targets/{mac_lint_podspecs.yaml => macos_lint_podspecs.yaml} (100%) rename .ci/targets/{mac_platform_tests.yaml => macos_platform_tests.yaml} (100%) diff --git a/.ci.yaml b/.ci.yaml index b99ed5460056..e9df5a639b3d 100644 --- a/.ci.yaml +++ b/.ci.yaml @@ -61,7 +61,7 @@ targets: properties: add_recipes_cq: "true" version_file: flutter_master.version - target_file: mac_lint_podspecs.yaml + target_file: macos_lint_podspecs.yaml ### macOS desktop tasks ### # macos-platform_tests builds all the plugins on ARM, so this build is run @@ -72,7 +72,7 @@ targets: properties: add_recipes_cq: "true" version_file: flutter_master.version - target_file: mac_build_all_plugins.yaml + target_file: macos_build_all_plugins.yaml channel: master - name: Mac_x64 build_all_plugins stable @@ -81,11 +81,10 @@ targets: properties: add_recipes_cq: "true" version_file: flutter_stable.version - target_file: mac_build_all_plugins.yaml + target_file: macos_build_all_plugins.yaml channel: stable - name: Mac_arm64 macos_platform_tests master - bringup: true # New task recipe: plugins/plugins timeout: 60 properties: @@ -95,7 +94,6 @@ targets: target_file: macos_platform_tests.yaml - name: Mac_arm64 macos_platform_tests stable - bringup: true # New task recipe: plugins/plugins presubmit: false timeout: 60 @@ -110,7 +108,6 @@ targets: # once simulator tests are reliable on the ARM infrastructure. See discussion # at https://github.com/flutter/plugins/pull/5693#issuecomment-1126011089 - name: Mac_arm64 ios_build_all_plugins master - bringup: true # New task recipe: plugins/plugins timeout: 30 properties: @@ -120,7 +117,6 @@ targets: target_file: ios_build_all_plugins.yaml - name: Mac_arm64 ios_build_all_plugins stable - bringup: true # New task recipe: plugins/plugins timeout: 30 properties: diff --git a/.ci/targets/mac_build_all_plugins.yaml b/.ci/targets/macos_build_all_plugins.yaml similarity index 100% rename from .ci/targets/mac_build_all_plugins.yaml rename to .ci/targets/macos_build_all_plugins.yaml diff --git a/.ci/targets/mac_lint_podspecs.yaml b/.ci/targets/macos_lint_podspecs.yaml similarity index 100% rename from .ci/targets/mac_lint_podspecs.yaml rename to .ci/targets/macos_lint_podspecs.yaml diff --git a/.ci/targets/mac_platform_tests.yaml b/.ci/targets/macos_platform_tests.yaml similarity index 100% rename from .ci/targets/mac_platform_tests.yaml rename to .ci/targets/macos_platform_tests.yaml diff --git a/.cirrus.yml b/.cirrus.yml index 4a5d604a081f..080dc914b0eb 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -22,21 +22,6 @@ tool_setup_template: &TOOL_SETUP_TEMPLATE tool_setup_script: - .ci/scripts/prepare_tool.sh -macos_template: &MACOS_TEMPLATE - # Only one macOS task can run in parallel without credits, so use them for - # PRs on macOS. - use_compute_credits: $CIRRUS_USER_COLLABORATOR == 'true' - -macos_intel_template: &MACOS_INTEL_TEMPLATE - << : *MACOS_TEMPLATE - osx_instance: - image: big-sur-xcode-13 - -macos_arm_template: &MACOS_ARM_TEMPLATE - << : *MACOS_TEMPLATE - macos_instance: - image: ghcr.io/cirruslabs/macos-ventura-xcode:14 - flutter_upgrade_template: &FLUTTER_UPGRADE_TEMPLATE upgrade_flutter_script: # Channels that are part of our normal test matrix use a pinned, @@ -306,37 +291,3 @@ task: - ./script/tool_runner.sh build-examples --web drive_script: - ./script/tool_runner.sh drive-examples --web --exclude=script/configs/exclude_integration_web.yaml - -# ARM macOS tasks. -task: - << : *MACOS_ARM_TEMPLATE - << : *FLUTTER_UPGRADE_TEMPLATE - matrix: - ### iOS tasks ### - - name: ios-build_all_plugins - env: - BUILD_ALL_ARGS: "ios --no-codesign" - matrix: - CHANNEL: "master" - CHANNEL: "stable" - << : *BUILD_ALL_PLUGINS_APP_TEMPLATE - ### macOS desktop tasks ### - - name: macos-platform_tests - # Don't run full platform tests on both channels in pre-submit. - skip: $CIRRUS_PR != '' && $CHANNEL == 'stable' - env: - matrix: - CHANNEL: "master" - CHANNEL: "stable" - PATH: $PATH:/usr/local/bin - build_script: - - ./script/tool_runner.sh build-examples --macos - xcode_analyze_script: - - ./script/tool_runner.sh xcode-analyze --macos - xcode_analyze_deprecation_script: - # Ensure we don't accidentally introduce deprecated code. - - ./script/tool_runner.sh xcode-analyze --macos --macos-min-version=12.3 - native_test_script: - - ./script/tool_runner.sh native-test --macos - drive_script: - - ./script/tool_runner.sh drive-examples --macos --exclude=script/configs/exclude_integration_macos.yaml From 2edf5632448f7e598dfbd775309aa3e35e5b2e02 Mon Sep 17 00:00:00 2001 From: stuartmorgan Date: Mon, 30 Jan 2023 18:34:06 -0800 Subject: [PATCH 049/130] [ci] Part 1 of swapping iOS platform test arch (#7064) * [ci] Part 1 of swapping iOS task arch Adds Intel versions of iOS build-all and ARM versions of iOS platform tests, as part one of swapping them. Once the new tasks propagate, they will be brought out of bringup mode and the old versions removed. These were on the opposite architectures because of issues with running the platfor tests on Cirrus ARM VMs, and then were ported as-is from Cirrus to LUCI, but now that macOS ARM works on LUCI we can switch to the desired configuration. * Increase sharding * Renaming * Also do stable --- .ci.yaml | 130 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) diff --git a/.ci.yaml b/.ci.yaml index e9df5a639b3d..c5b6adff9108 100644 --- a/.ci.yaml +++ b/.ci.yaml @@ -125,6 +125,26 @@ targets: version_file: flutter_stable.version target_file: ios_build_all_plugins.yaml + - name: Mac_x64 ios_build_all_plugins master + bringup: true # New task, replaces ARM version + recipe: plugins/plugins + timeout: 30 + properties: + channel: master + add_recipes_cq: "true" + version_file: flutter_master.version + target_file: ios_build_all_plugins.yaml + + - name: Mac_x64 ios_build_all_plugins stable + bringup: true # New task, replaces ARM version + recipe: plugins/plugins + timeout: 30 + properties: + channel: stable + add_recipes_cq: "true" + version_file: flutter_stable.version + target_file: ios_build_all_plugins.yaml + # TODO(stuartmorgan): Swap the architecture of this and ios_build_all_plugins # once simulator tests are reliable on the ARM infrastructure. See discussion # at https://github.com/flutter/plugins/pull/5693#issuecomment-1126011089 @@ -164,6 +184,56 @@ targets: target_file: ios_platform_tests.yaml package_sharding: "--shardIndex 3 --shardCount 4" + - name: Mac_arm64 ios_platform_tests_shard_1 master - plugins + bringup: true # New task; will replace Intel version + recipe: plugins/plugins + timeout: 60 + properties: + add_recipes_cq: "true" + version_file: flutter_master.version + target_file: ios_platform_tests.yaml + package_sharding: "--shardIndex 0 --shardCount 5" + + - name: Mac_arm64 ios_platform_tests_shard_2 master - plugins + bringup: true # New task; will replace Intel version + recipe: plugins/plugins + timeout: 60 + properties: + add_recipes_cq: "true" + version_file: flutter_master.version + target_file: ios_platform_tests.yaml + package_sharding: "--shardIndex 1 --shardCount 5" + + - name: Mac_arm64 ios_platform_tests_shard_3 master - plugins + bringup: true # New task; will replace Intel version + recipe: plugins/plugins + timeout: 60 + properties: + add_recipes_cq: "true" + version_file: flutter_master.version + target_file: ios_platform_tests.yaml + package_sharding: "--shardIndex 2 --shardCount 5" + + - name: Mac_arm64 ios_platform_tests_shard_4 master - plugins + bringup: true # New task; will replace Intel version + recipe: plugins/plugins + timeout: 60 + properties: + add_recipes_cq: "true" + version_file: flutter_master.version + target_file: ios_platform_tests.yaml + package_sharding: "--shardIndex 3 --shardCount 5" + + - name: Mac_arm64 ios_platform_tests_shard_5 master - plugins + bringup: true # New task; will replace Intel version + recipe: plugins/plugins + timeout: 60 + properties: + add_recipes_cq: "true" + version_file: flutter_master.version + target_file: ios_platform_tests.yaml + package_sharding: "--shardIndex 4 --shardCount 5" + # Don't run full platform tests on both channels in pre-submit. - name: Mac_x64 ios_platform_tests_1_of_4 stable recipe: plugins/plugins @@ -209,6 +279,66 @@ targets: target_file: ios_platform_tests.yaml package_sharding: "--shardIndex 3 --shardCount 4" + - name: Mac_arm64 ios_platform_tests_shard_1 stable - plugins + bringup: true # New task; will replace Intel version + recipe: plugins/plugins + presubmit: false + timeout: 60 + properties: + channel: stable + add_recipes_cq: "true" + version_file: flutter_stable.version + target_file: ios_platform_tests.yaml + package_sharding: "--shardIndex 0 --shardCount 5" + + - name: Mac_arm64 ios_platform_tests_shard_2 stable - plugins + bringup: true # New task; will replace Intel version + recipe: plugins/plugins + presubmit: false + timeout: 60 + properties: + channel: stable + add_recipes_cq: "true" + version_file: flutter_stable.version + target_file: ios_platform_tests.yaml + package_sharding: "--shardIndex 1 --shardCount 5" + + - name: Mac_arm64 ios_platform_tests_shard_3 stable - plugins + bringup: true # New task; will replace Intel version + recipe: plugins/plugins + presubmit: false + timeout: 60 + properties: + channel: stable + add_recipes_cq: "true" + version_file: flutter_stable.version + target_file: ios_platform_tests.yaml + package_sharding: "--shardIndex 2 --shardCount 5" + + - name: Mac_arm64 ios_platform_tests_shard_4 stable - plugins + bringup: true # New task; will replace Intel version + recipe: plugins/plugins + presubmit: false + timeout: 60 + properties: + channel: stable + add_recipes_cq: "true" + version_file: flutter_stable.version + target_file: ios_platform_tests.yaml + package_sharding: "--shardIndex 3 --shardCount 5" + + - name: Mac_arm64 ios_platform_tests_shard_5 stable - plugins + bringup: true # New task; will replace Intel version + recipe: plugins/plugins + presubmit: false + timeout: 60 + properties: + channel: stable + add_recipes_cq: "true" + version_file: flutter_stable.version + target_file: ios_platform_tests.yaml + package_sharding: "--shardIndex 4 --shardCount 5" + - name: Windows win32-platform_tests master recipe: plugins/plugins timeout: 60 From 35f0b1a491f6ea5b6b2cb2349905cdb6c09c70f6 Mon Sep 17 00:00:00 2001 From: Camille Simon <43054281+camsim99@users.noreply.github.com> Date: Mon, 30 Jan 2023 18:34:07 -0800 Subject: [PATCH 050/130] [camerax] Add system services to plugin (#6986) * Add base code from proof of concept * Add pigeon types * Make corrections, add Dart tests * Add test files * Add java tests * Add docs * Add stop, docs, fix analyze * Fix comment * Add comment and remove literals * Formatting * Remove merge conflict reside * Fix test * Fix bad pasting job --- .../android/src/main/AndroidManifest.xml | 5 + .../camerax/CameraAndroidCameraxPlugin.java | 16 +- .../camerax/CameraPermissionsManager.java | 120 +++++++ .../flutter/plugins/camerax/CameraXProxy.java | 13 + .../camerax/DeviceOrientationManager.java | 329 ++++++++++++++++++ .../camerax/GeneratedCameraXLibrary.java | 255 ++++++++++++++ .../camerax/SystemServicesFlutterApiImpl.java | 22 ++ .../camerax/SystemServicesHostApiImpl.java | 112 ++++++ .../camerax/CameraPermissionsManagerTest.java | 89 +++++ .../camerax/DeviceOrientationManagerTest.java | 313 +++++++++++++++++ .../plugins/camerax/SystemServicesTest.java | 137 ++++++++ ...roid_camera_camerax_flutter_api_impls.dart | 8 + .../lib/src/camerax_library.pigeon.dart | 172 +++++++++ .../lib/src/system_services.dart | 137 ++++++++ .../pigeons/camerax_library.dart | 26 ++ .../camera_android_camerax/pubspec.yaml | 1 + .../test/system_services_test.dart | 101 ++++++ .../test/system_services_test.mocks.dart | 66 ++++ .../test/test_camerax_library.pigeon.dart | 97 ++++++ 19 files changed, 2016 insertions(+), 3 deletions(-) create mode 100644 packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraPermissionsManager.java create mode 100644 packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/DeviceOrientationManager.java create mode 100644 packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/SystemServicesFlutterApiImpl.java create mode 100644 packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/SystemServicesHostApiImpl.java create mode 100644 packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraPermissionsManagerTest.java create mode 100644 packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/DeviceOrientationManagerTest.java create mode 100644 packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/SystemServicesTest.java create mode 100644 packages/camera/camera_android_camerax/lib/src/system_services.dart create mode 100644 packages/camera/camera_android_camerax/test/system_services_test.dart create mode 100644 packages/camera/camera_android_camerax/test/system_services_test.mocks.dart diff --git a/packages/camera/camera_android_camerax/android/src/main/AndroidManifest.xml b/packages/camera/camera_android_camerax/android/src/main/AndroidManifest.xml index ea4275c757cf..52012aaa6915 100644 --- a/packages/camera/camera_android_camerax/android/src/main/AndroidManifest.xml +++ b/packages/camera/camera_android_camerax/android/src/main/AndroidManifest.xml @@ -1,3 +1,8 @@ + + + + diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraAndroidCameraxPlugin.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraAndroidCameraxPlugin.java index 7ee7263f7779..c35394f01d82 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraAndroidCameraxPlugin.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraAndroidCameraxPlugin.java @@ -11,12 +11,14 @@ import io.flutter.embedding.engine.plugins.activity.ActivityAware; import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.view.TextureRegistry; /** Platform implementation of the camera_plugin implemented with the CameraX library. */ public final class CameraAndroidCameraxPlugin implements FlutterPlugin, ActivityAware { private InstanceManager instanceManager; private FlutterPluginBinding pluginBinding; - public ProcessCameraProviderHostApiImpl processCameraProviderHostApi; + private ProcessCameraProviderHostApiImpl processCameraProviderHostApi; + public SystemServicesHostApiImpl systemServicesHostApi; /** * Initialize this within the {@code #configureFlutterEngine} of a Flutter activity or fragment. @@ -25,7 +27,7 @@ public final class CameraAndroidCameraxPlugin implements FlutterPlugin, Activity */ public CameraAndroidCameraxPlugin() {} - void setUp(BinaryMessenger binaryMessenger, Context context) { + void setUp(BinaryMessenger binaryMessenger, Context context, TextureRegistry textureRegistry) { // Set up instance manager. instanceManager = InstanceManager.open( @@ -45,6 +47,8 @@ void setUp(BinaryMessenger binaryMessenger, Context context) { new ProcessCameraProviderHostApiImpl(binaryMessenger, instanceManager, context); GeneratedCameraXLibrary.ProcessCameraProviderHostApi.setup( binaryMessenger, processCameraProviderHostApi); + systemServicesHostApi = new SystemServicesHostApiImpl(binaryMessenger, instanceManager); + GeneratedCameraXLibrary.SystemServicesHostApi.setup(binaryMessenger, systemServicesHostApi); } @Override @@ -63,10 +67,16 @@ public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { @Override public void onAttachedToActivity(@NonNull ActivityPluginBinding activityPluginBinding) { - setUp(pluginBinding.getBinaryMessenger(), pluginBinding.getApplicationContext()); + setUp( + pluginBinding.getBinaryMessenger(), + pluginBinding.getApplicationContext(), + pluginBinding.getTextureRegistry()); updateContext(pluginBinding.getApplicationContext()); processCameraProviderHostApi.setLifecycleOwner( (LifecycleOwner) activityPluginBinding.getActivity()); + systemServicesHostApi.setActivity(activityPluginBinding.getActivity()); + systemServicesHostApi.setPermissionsRegistry( + activityPluginBinding::addRequestPermissionsResultListener); } @Override diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraPermissionsManager.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraPermissionsManager.java new file mode 100644 index 000000000000..19b1ee569a9b --- /dev/null +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraPermissionsManager.java @@ -0,0 +1,120 @@ +// Copyright 2013 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.camerax; + +import android.Manifest; +import android.Manifest.permission; +import android.app.Activity; +import android.content.pm.PackageManager; +import androidx.annotation.VisibleForTesting; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; + +final class CameraPermissionsManager { + interface PermissionsRegistry { + @SuppressWarnings("deprecation") + void addListener( + io.flutter.plugin.common.PluginRegistry.RequestPermissionsResultListener handler); + } + + interface ResultCallback { + void onResult(String errorCode, String errorDescription); + } + + /** + * Camera access permission errors handled when camera is created. See {@code MethodChannelCamera} + * in {@code camera/camera_platform_interface} for details. + */ + private static final String CAMERA_PERMISSIONS_REQUEST_ONGOING = + "CameraPermissionsRequestOngoing"; + + private static final String CAMERA_PERMISSIONS_REQUEST_ONGOING_MESSAGE = + "Another request is ongoing and multiple requests cannot be handled at once."; + private static final String CAMERA_ACCESS_DENIED = "CameraAccessDenied"; + private static final String CAMERA_ACCESS_DENIED_MESSAGE = "Camera access permission was denied."; + private static final String AUDIO_ACCESS_DENIED = "AudioAccessDenied"; + private static final String AUDIO_ACCESS_DENIED_MESSAGE = "Audio access permission was denied."; + + private static final int CAMERA_REQUEST_ID = 9796; + @VisibleForTesting boolean ongoing = false; + + void requestPermissions( + Activity activity, + PermissionsRegistry permissionsRegistry, + boolean enableAudio, + ResultCallback callback) { + if (ongoing) { + callback.onResult( + CAMERA_PERMISSIONS_REQUEST_ONGOING, CAMERA_PERMISSIONS_REQUEST_ONGOING_MESSAGE); + return; + } + if (!hasCameraPermission(activity) || (enableAudio && !hasAudioPermission(activity))) { + permissionsRegistry.addListener( + new CameraRequestPermissionsListener( + (String errorCode, String errorDescription) -> { + ongoing = false; + callback.onResult(errorCode, errorDescription); + })); + ongoing = true; + ActivityCompat.requestPermissions( + activity, + enableAudio + ? new String[] {Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO} + : new String[] {Manifest.permission.CAMERA}, + CAMERA_REQUEST_ID); + } else { + // Permissions already exist. Call the callback with success. + callback.onResult(null, null); + } + } + + private boolean hasCameraPermission(Activity activity) { + return ContextCompat.checkSelfPermission(activity, permission.CAMERA) + == PackageManager.PERMISSION_GRANTED; + } + + private boolean hasAudioPermission(Activity activity) { + return ContextCompat.checkSelfPermission(activity, permission.RECORD_AUDIO) + == PackageManager.PERMISSION_GRANTED; + } + + @VisibleForTesting + @SuppressWarnings("deprecation") + static final class CameraRequestPermissionsListener + implements io.flutter.plugin.common.PluginRegistry.RequestPermissionsResultListener { + + // There's no way to unregister permission listeners in the v1 embedding, so we'll be called + // duplicate times in cases where the user denies and then grants a permission. Keep track of if + // we've responded before and bail out of handling the callback manually if this is a repeat + // call. + boolean alreadyCalled = false; + + final ResultCallback callback; + + @VisibleForTesting + CameraRequestPermissionsListener(ResultCallback callback) { + this.callback = callback; + } + + @Override + public boolean onRequestPermissionsResult(int id, String[] permissions, int[] grantResults) { + if (alreadyCalled || id != CAMERA_REQUEST_ID) { + return false; + } + + alreadyCalled = true; + // grantResults could be empty if the permissions request with the user is interrupted + // https://developer.android.com/reference/android/app/Activity#onRequestPermissionsResult(int,%20java.lang.String[],%20int[]) + if (grantResults.length == 0 || grantResults[0] != PackageManager.PERMISSION_GRANTED) { + callback.onResult(CAMERA_ACCESS_DENIED, CAMERA_ACCESS_DENIED_MESSAGE); + } else if (grantResults.length > 1 && grantResults[1] != PackageManager.PERMISSION_GRANTED) { + callback.onResult(AUDIO_ACCESS_DENIED, AUDIO_ACCESS_DENIED_MESSAGE); + } else { + callback.onResult(null, null); + } + return true; + } + } +} diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraXProxy.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraXProxy.java index 8063866d2fc6..83c43a9d55d4 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraXProxy.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraXProxy.java @@ -4,10 +4,23 @@ package io.flutter.plugins.camerax; +import android.app.Activity; import androidx.camera.core.CameraSelector; public class CameraXProxy { public CameraSelector.Builder createCameraSelectorBuilder() { return new CameraSelector.Builder(); } + + public CameraPermissionsManager createCameraPermissionsManager() { + return new CameraPermissionsManager(); + } + + public DeviceOrientationManager createDeviceOrientationManager( + Activity activity, + Boolean isFrontFacing, + int sensorOrientation, + DeviceOrientationManager.DeviceOrientationChangeCallback callback) { + return new DeviceOrientationManager(activity, isFrontFacing, sensorOrientation, callback); + } } diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/DeviceOrientationManager.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/DeviceOrientationManager.java new file mode 100644 index 000000000000..ebcb86433f65 --- /dev/null +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/DeviceOrientationManager.java @@ -0,0 +1,329 @@ +// Copyright 2013 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.camerax; + +import android.app.Activity; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.res.Configuration; +import android.view.Display; +import android.view.Surface; +import android.view.WindowManager; +import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; +import io.flutter.embedding.engine.systemchannels.PlatformChannel; +import io.flutter.embedding.engine.systemchannels.PlatformChannel.DeviceOrientation; + +/** + * Support class to help to determine the media orientation based on the orientation of the device. + */ +public class DeviceOrientationManager { + + interface DeviceOrientationChangeCallback { + void onChange(DeviceOrientation newOrientation); + } + + private static final IntentFilter orientationIntentFilter = + new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED); + + private final Activity activity; + private final boolean isFrontFacing; + private final int sensorOrientation; + private final DeviceOrientationChangeCallback deviceOrientationChangeCallback; + private PlatformChannel.DeviceOrientation lastOrientation; + private BroadcastReceiver broadcastReceiver; + + DeviceOrientationManager( + @NonNull Activity activity, + boolean isFrontFacing, + int sensorOrientation, + DeviceOrientationChangeCallback callback) { + this.activity = activity; + this.isFrontFacing = isFrontFacing; + this.sensorOrientation = sensorOrientation; + this.deviceOrientationChangeCallback = callback; + } + + /** + * Starts listening to the device's sensors or UI for orientation updates. + * + *

When orientation information is updated, the callback method of the {@link + * DeviceOrientationChangeCallback} is called with the new orientation. This latest value can also + * be retrieved through the {@link #getVideoOrientation()} accessor. + * + *

If the device's ACCELEROMETER_ROTATION setting is enabled the {@link + * DeviceOrientationManager} will report orientation updates based on the sensor information. If + * the ACCELEROMETER_ROTATION is disabled the {@link DeviceOrientationManager} will fallback to + * the deliver orientation updates based on the UI orientation. + */ + public void start() { + if (broadcastReceiver != null) { + return; + } + broadcastReceiver = + new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + handleUIOrientationChange(); + } + }; + activity.registerReceiver(broadcastReceiver, orientationIntentFilter); + broadcastReceiver.onReceive(activity, null); + } + + /** Stops listening for orientation updates. */ + public void stop() { + if (broadcastReceiver == null) { + return; + } + activity.unregisterReceiver(broadcastReceiver); + broadcastReceiver = null; + } + + /** + * Returns the device's photo orientation in degrees based on the sensor orientation and the last + * known UI orientation. + * + *

Returns one of 0, 90, 180 or 270. + * + * @return The device's photo orientation in degrees. + */ + public int getPhotoOrientation() { + return this.getPhotoOrientation(this.lastOrientation); + } + + /** + * Returns the device's photo orientation in degrees based on the sensor orientation and the + * supplied {@link PlatformChannel.DeviceOrientation} value. + * + *

Returns one of 0, 90, 180 or 270. + * + * @param orientation The {@link PlatformChannel.DeviceOrientation} value that is to be converted + * into degrees. + * @return The device's photo orientation in degrees. + */ + public int getPhotoOrientation(PlatformChannel.DeviceOrientation orientation) { + int angle = 0; + // Fallback to device orientation when the orientation value is null. + if (orientation == null) { + orientation = getUIOrientation(); + } + + switch (orientation) { + case PORTRAIT_UP: + angle = 90; + break; + case PORTRAIT_DOWN: + angle = 270; + break; + case LANDSCAPE_LEFT: + angle = isFrontFacing ? 180 : 0; + break; + case LANDSCAPE_RIGHT: + angle = isFrontFacing ? 0 : 180; + break; + } + + // Sensor orientation is 90 for most devices, or 270 for some devices (eg. Nexus 5X). + // This has to be taken into account so the JPEG is rotated properly. + // For devices with orientation of 90, this simply returns the mapping from ORIENTATIONS. + // For devices with orientation of 270, the JPEG is rotated 180 degrees instead. + return (angle + sensorOrientation + 270) % 360; + } + + /** + * Returns the device's video orientation in clockwise degrees based on the sensor orientation and + * the last known UI orientation. + * + *

Returns one of 0, 90, 180 or 270. + * + * @return The device's video orientation in clockwise degrees. + */ + public int getVideoOrientation() { + return this.getVideoOrientation(this.lastOrientation); + } + + /** + * Returns the device's video orientation in clockwise degrees based on the sensor orientation and + * the supplied {@link PlatformChannel.DeviceOrientation} value. + * + *

Returns one of 0, 90, 180 or 270. + * + *

More details can be found in the official Android documentation: + * https://developer.android.com/reference/android/media/MediaRecorder#setOrientationHint(int) + * + *

See also: + * https://developer.android.com/training/camera2/camera-preview-large-screens#orientation_calculation + * + * @param orientation The {@link PlatformChannel.DeviceOrientation} value that is to be converted + * into degrees. + * @return The device's video orientation in clockwise degrees. + */ + public int getVideoOrientation(PlatformChannel.DeviceOrientation orientation) { + int angle = 0; + + // Fallback to device orientation when the orientation value is null. + if (orientation == null) { + orientation = getUIOrientation(); + } + + switch (orientation) { + case PORTRAIT_UP: + angle = 0; + break; + case PORTRAIT_DOWN: + angle = 180; + break; + case LANDSCAPE_LEFT: + angle = 270; + break; + case LANDSCAPE_RIGHT: + angle = 90; + break; + } + + if (isFrontFacing) { + angle *= -1; + } + + return (angle + sensorOrientation + 360) % 360; + } + + /** @return the last received UI orientation. */ + public PlatformChannel.DeviceOrientation getLastUIOrientation() { + return this.lastOrientation; + } + + /** + * Handles orientation changes based on change events triggered by the OrientationIntentFilter. + * + *

This method is visible for testing purposes only and should never be used outside this + * class. + */ + @VisibleForTesting + void handleUIOrientationChange() { + PlatformChannel.DeviceOrientation orientation = getUIOrientation(); + handleOrientationChange(orientation, lastOrientation, deviceOrientationChangeCallback); + lastOrientation = orientation; + } + + /** + * Handles orientation changes coming from either the device's sensors or the + * OrientationIntentFilter. + * + *

This method is visible for testing purposes only and should never be used outside this + * class. + */ + @VisibleForTesting + static void handleOrientationChange( + DeviceOrientation newOrientation, + DeviceOrientation previousOrientation, + DeviceOrientationChangeCallback callback) { + if (!newOrientation.equals(previousOrientation)) { + callback.onChange(newOrientation); + } + } + + /** + * Gets the current user interface orientation. + * + *

This method is visible for testing purposes only and should never be used outside this + * class. + * + * @return The current user interface orientation. + */ + @VisibleForTesting + PlatformChannel.DeviceOrientation getUIOrientation() { + final int rotation = getDisplay().getRotation(); + final int orientation = activity.getResources().getConfiguration().orientation; + + switch (orientation) { + case Configuration.ORIENTATION_PORTRAIT: + if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_90) { + return PlatformChannel.DeviceOrientation.PORTRAIT_UP; + } else { + return PlatformChannel.DeviceOrientation.PORTRAIT_DOWN; + } + case Configuration.ORIENTATION_LANDSCAPE: + if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_90) { + return PlatformChannel.DeviceOrientation.LANDSCAPE_LEFT; + } else { + return PlatformChannel.DeviceOrientation.LANDSCAPE_RIGHT; + } + default: + return PlatformChannel.DeviceOrientation.PORTRAIT_UP; + } + } + + /** + * Calculates the sensor orientation based on the supplied angle. + * + *

This method is visible for testing purposes only and should never be used outside this + * class. + * + * @param angle Orientation angle. + * @return The sensor orientation based on the supplied angle. + */ + @VisibleForTesting + PlatformChannel.DeviceOrientation calculateSensorOrientation(int angle) { + final int tolerance = 45; + angle += tolerance; + + // Orientation is 0 in the default orientation mode. This is portrait-mode for phones + // and landscape for tablets. We have to compensate for this by calculating the default + // orientation, and apply an offset accordingly. + int defaultDeviceOrientation = getDeviceDefaultOrientation(); + if (defaultDeviceOrientation == Configuration.ORIENTATION_LANDSCAPE) { + angle += 90; + } + // Determine the orientation + angle = angle % 360; + return new PlatformChannel.DeviceOrientation[] { + PlatformChannel.DeviceOrientation.PORTRAIT_UP, + PlatformChannel.DeviceOrientation.LANDSCAPE_LEFT, + PlatformChannel.DeviceOrientation.PORTRAIT_DOWN, + PlatformChannel.DeviceOrientation.LANDSCAPE_RIGHT, + } + [angle / 90]; + } + + /** + * Gets the default orientation of the device. + * + *

This method is visible for testing purposes only and should never be used outside this + * class. + * + * @return The default orientation of the device. + */ + @VisibleForTesting + int getDeviceDefaultOrientation() { + Configuration config = activity.getResources().getConfiguration(); + int rotation = getDisplay().getRotation(); + if (((rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) + && config.orientation == Configuration.ORIENTATION_LANDSCAPE) + || ((rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) + && config.orientation == Configuration.ORIENTATION_PORTRAIT)) { + return Configuration.ORIENTATION_LANDSCAPE; + } else { + return Configuration.ORIENTATION_PORTRAIT; + } + } + + /** + * Gets an instance of the Android {@link android.view.Display}. + * + *

This method is visible for testing purposes only and should never be used outside this + * class. + * + * @return An instance of the Android {@link android.view.Display}. + */ + @SuppressWarnings("deprecation") + @VisibleForTesting + Display getDisplay() { + return ((WindowManager) activity.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); + } +} diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/GeneratedCameraXLibrary.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/GeneratedCameraXLibrary.java index 8c42a7911768..528870cc749c 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/GeneratedCameraXLibrary.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/GeneratedCameraXLibrary.java @@ -13,6 +13,8 @@ import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.MessageCodec; import io.flutter.plugin.common.StandardMessageCodec; +import java.io.ByteArrayOutputStream; +import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -23,6 +25,78 @@ @SuppressWarnings({"unused", "unchecked", "CodeBlock2Expr", "RedundantSuppression"}) public class GeneratedCameraXLibrary { + /** Generated class from Pigeon that represents data sent in messages. */ + public static class CameraPermissionsErrorData { + private @NonNull String errorCode; + + public @NonNull String getErrorCode() { + return errorCode; + } + + public void setErrorCode(@NonNull String setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"errorCode\" is null."); + } + this.errorCode = setterArg; + } + + private @NonNull String description; + + public @NonNull String getDescription() { + return description; + } + + public void setDescription(@NonNull String setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"description\" is null."); + } + this.description = setterArg; + } + + /** Constructor is private to enforce null safety; use Builder. */ + private CameraPermissionsErrorData() {} + + public static final class Builder { + private @Nullable String errorCode; + + public @NonNull Builder setErrorCode(@NonNull String setterArg) { + this.errorCode = setterArg; + return this; + } + + private @Nullable String description; + + public @NonNull Builder setDescription(@NonNull String setterArg) { + this.description = setterArg; + return this; + } + + public @NonNull CameraPermissionsErrorData build() { + CameraPermissionsErrorData pigeonReturn = new CameraPermissionsErrorData(); + pigeonReturn.setErrorCode(errorCode); + pigeonReturn.setDescription(description); + return pigeonReturn; + } + } + + @NonNull + Map toMap() { + Map toMapResult = new HashMap<>(); + toMapResult.put("errorCode", errorCode); + toMapResult.put("description", description); + return toMapResult; + } + + static @NonNull CameraPermissionsErrorData fromMap(@NonNull Map map) { + CameraPermissionsErrorData pigeonResult = new CameraPermissionsErrorData(); + Object errorCode = map.get("errorCode"); + pigeonResult.setErrorCode((String) errorCode); + Object description = map.get("description"); + pigeonResult.setDescription((String) description); + return pigeonResult; + } + } + public interface Result { void success(T result); @@ -590,6 +664,187 @@ public void create(@NonNull Long identifierArg, Reply callback) { } } + private static class SystemServicesHostApiCodec extends StandardMessageCodec { + public static final SystemServicesHostApiCodec INSTANCE = new SystemServicesHostApiCodec(); + + private SystemServicesHostApiCodec() {} + + @Override + protected Object readValueOfType(byte type, ByteBuffer buffer) { + switch (type) { + case (byte) 128: + return CameraPermissionsErrorData.fromMap((Map) readValue(buffer)); + + default: + return super.readValueOfType(type, buffer); + } + } + + @Override + protected void writeValue(ByteArrayOutputStream stream, Object value) { + if (value instanceof CameraPermissionsErrorData) { + stream.write(128); + writeValue(stream, ((CameraPermissionsErrorData) value).toMap()); + } else { + super.writeValue(stream, value); + } + } + } + + /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ + public interface SystemServicesHostApi { + void requestCameraPermissions( + @NonNull Boolean enableAudio, Result result); + + void startListeningForDeviceOrientationChange( + @NonNull Boolean isFrontFacing, @NonNull Long sensorOrientation); + + void stopListeningForDeviceOrientationChange(); + + /** The codec used by SystemServicesHostApi. */ + static MessageCodec getCodec() { + return SystemServicesHostApiCodec.INSTANCE; + } + + /** + * Sets up an instance of `SystemServicesHostApi` to handle messages through the + * `binaryMessenger`. + */ + static void setup(BinaryMessenger binaryMessenger, SystemServicesHostApi api) { + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.SystemServicesHostApi.requestCameraPermissions", + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + Map wrapped = new HashMap<>(); + try { + ArrayList args = (ArrayList) message; + Boolean enableAudioArg = (Boolean) args.get(0); + if (enableAudioArg == null) { + throw new NullPointerException("enableAudioArg unexpectedly null."); + } + Result resultCallback = + new Result() { + public void success(CameraPermissionsErrorData result) { + wrapped.put("result", result); + reply.reply(wrapped); + } + + public void error(Throwable error) { + wrapped.put("error", wrapError(error)); + reply.reply(wrapped); + } + }; + + api.requestCameraPermissions(enableAudioArg, resultCallback); + } catch (Error | RuntimeException exception) { + wrapped.put("error", wrapError(exception)); + reply.reply(wrapped); + } + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.SystemServicesHostApi.startListeningForDeviceOrientationChange", + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + Map wrapped = new HashMap<>(); + try { + ArrayList args = (ArrayList) message; + Boolean isFrontFacingArg = (Boolean) args.get(0); + if (isFrontFacingArg == null) { + throw new NullPointerException("isFrontFacingArg unexpectedly null."); + } + Number sensorOrientationArg = (Number) args.get(1); + if (sensorOrientationArg == null) { + throw new NullPointerException("sensorOrientationArg unexpectedly null."); + } + api.startListeningForDeviceOrientationChange( + isFrontFacingArg, + (sensorOrientationArg == null) ? null : sensorOrientationArg.longValue()); + wrapped.put("result", null); + } catch (Error | RuntimeException exception) { + wrapped.put("error", wrapError(exception)); + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.SystemServicesHostApi.stopListeningForDeviceOrientationChange", + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + Map wrapped = new HashMap<>(); + try { + api.stopListeningForDeviceOrientationChange(); + wrapped.put("result", null); + } catch (Error | RuntimeException exception) { + wrapped.put("error", wrapError(exception)); + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + } + } + + private static class SystemServicesFlutterApiCodec extends StandardMessageCodec { + public static final SystemServicesFlutterApiCodec INSTANCE = + new SystemServicesFlutterApiCodec(); + + private SystemServicesFlutterApiCodec() {} + } + + /** Generated class from Pigeon that represents Flutter messages that can be called from Java. */ + public static class SystemServicesFlutterApi { + private final BinaryMessenger binaryMessenger; + + public SystemServicesFlutterApi(BinaryMessenger argBinaryMessenger) { + this.binaryMessenger = argBinaryMessenger; + } + + public interface Reply { + void reply(T reply); + } + + static MessageCodec getCodec() { + return SystemServicesFlutterApiCodec.INSTANCE; + } + + public void onDeviceOrientationChanged(@NonNull String orientationArg, Reply callback) { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.SystemServicesFlutterApi.onDeviceOrientationChanged", + getCodec()); + channel.send( + new ArrayList(Arrays.asList(orientationArg)), + channelReply -> { + callback.reply(null); + }); + } + } + private static Map wrapError(Throwable exception) { Map errorMap = new HashMap<>(); errorMap.put("message", exception.toString()); diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/SystemServicesFlutterApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/SystemServicesFlutterApiImpl.java new file mode 100644 index 000000000000..1e9f33b092bb --- /dev/null +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/SystemServicesFlutterApiImpl.java @@ -0,0 +1,22 @@ +// Copyright 2013 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.camerax; + +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugins.camerax.GeneratedCameraXLibrary.SystemServicesFlutterApi; + +public class SystemServicesFlutterApiImpl extends SystemServicesFlutterApi { + public SystemServicesFlutterApiImpl( + BinaryMessenger binaryMessenger, InstanceManager instanceManager) { + super(binaryMessenger); + this.instanceManager = instanceManager; + } + + private final InstanceManager instanceManager; + + public void onDeviceOrientationChanged(String orientation, Reply reply) { + super.onDeviceOrientationChanged(orientation, reply); + } +} diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/SystemServicesHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/SystemServicesHostApiImpl.java new file mode 100644 index 000000000000..e8eb715a7b3a --- /dev/null +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/SystemServicesHostApiImpl.java @@ -0,0 +1,112 @@ +// Copyright 2013 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.camerax; + +import android.app.Activity; +import androidx.annotation.VisibleForTesting; +import io.flutter.embedding.engine.systemchannels.PlatformChannel.DeviceOrientation; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugins.camerax.CameraPermissionsManager.PermissionsRegistry; +import io.flutter.plugins.camerax.GeneratedCameraXLibrary.CameraPermissionsErrorData; +import io.flutter.plugins.camerax.GeneratedCameraXLibrary.Result; +import io.flutter.plugins.camerax.GeneratedCameraXLibrary.SystemServicesHostApi; + +public class SystemServicesHostApiImpl implements SystemServicesHostApi { + private final BinaryMessenger binaryMessenger; + private final InstanceManager instanceManager; + + @VisibleForTesting public CameraXProxy cameraXProxy = new CameraXProxy(); + @VisibleForTesting public DeviceOrientationManager deviceOrientationManager; + @VisibleForTesting public SystemServicesFlutterApiImpl systemServicesFlutterApi; + + private Activity activity; + private PermissionsRegistry permissionsRegistry; + + public SystemServicesHostApiImpl( + BinaryMessenger binaryMessenger, InstanceManager instanceManager) { + this.binaryMessenger = binaryMessenger; + this.instanceManager = instanceManager; + this.systemServicesFlutterApi = + new SystemServicesFlutterApiImpl(binaryMessenger, instanceManager); + } + + public void setActivity(Activity activity) { + this.activity = activity; + } + + public void setPermissionsRegistry(PermissionsRegistry permissionsRegistry) { + this.permissionsRegistry = permissionsRegistry; + } + + /** + * Requests camera permissions using an instance of a {@link CameraPermissionsManager}. + * + *

Will result with {@code null} if permissions were approved or there were no errors; + * otherwise, it will result with the error data explaining what went wrong. + */ + @Override + public void requestCameraPermissions( + Boolean enableAudio, Result result) { + CameraPermissionsManager cameraPermissionsManager = + cameraXProxy.createCameraPermissionsManager(); + cameraPermissionsManager.requestPermissions( + activity, + permissionsRegistry, + enableAudio, + (String errorCode, String description) -> { + if (errorCode == null) { + result.success(null); + } else { + // If permissions are ongoing or denied, error data will be sent to be handled. + CameraPermissionsErrorData errorData = + new CameraPermissionsErrorData.Builder() + .setErrorCode(errorCode) + .setDescription(description) + .build(); + result.success(errorData); + } + }); + } + + /** + * Starts listening for device orientation changes using an instace of a {@link + * DeviceOrientationManager}. + * + *

Whenever a change in device orientation is detected by the {@code DeviceOrientationManager}, + * the {@link SystemServicesFlutterApi} will be used to notify the Dart side. + */ + @Override + public void startListeningForDeviceOrientationChange( + Boolean isFrontFacing, Long sensorOrientation) { + deviceOrientationManager = + cameraXProxy.createDeviceOrientationManager( + activity, + isFrontFacing, + sensorOrientation.intValue(), + (DeviceOrientation newOrientation) -> { + systemServicesFlutterApi.onDeviceOrientationChanged( + serializeDeviceOrientation(newOrientation), reply -> {}); + }); + deviceOrientationManager.start(); + } + + /** Serializes {@code DeviceOrientation} into a String that the Dart side is able to recognize. */ + String serializeDeviceOrientation(DeviceOrientation orientation) { + return orientation.toString(); + } + + /** + * Tells the {@code deviceOrientationManager} to stop listening for orientation updates. + * + *

Has no effect if the {@code deviceOrientationManager} was never created to listen for device + * orientation updates. + */ + @Override + public void stopListeningForDeviceOrientationChange() { + if (deviceOrientationManager != null) { + deviceOrientationManager.stop(); + } + } +} diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraPermissionsManagerTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraPermissionsManagerTest.java new file mode 100644 index 000000000000..d90bde953306 --- /dev/null +++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraPermissionsManagerTest.java @@ -0,0 +1,89 @@ +// Copyright 2013 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.camerax; + +import static junit.framework.TestCase.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import android.content.pm.PackageManager; +import io.flutter.plugins.camerax.CameraPermissionsManager.CameraRequestPermissionsListener; +import io.flutter.plugins.camerax.CameraPermissionsManager.ResultCallback; +import org.junit.Test; + +public class CameraPermissionsManagerTest { + @Test + public void listener_respondsOnce() { + final int[] calledCounter = {0}; + CameraRequestPermissionsListener permissionsListener = + new CameraRequestPermissionsListener((String code, String desc) -> calledCounter[0]++); + + permissionsListener.onRequestPermissionsResult( + 9796, null, new int[] {PackageManager.PERMISSION_DENIED}); + permissionsListener.onRequestPermissionsResult( + 9796, null, new int[] {PackageManager.PERMISSION_GRANTED}); + + assertEquals(1, calledCounter[0]); + } + + @Test + public void callback_respondsWithCameraAccessDenied() { + ResultCallback fakeResultCallback = mock(ResultCallback.class); + CameraRequestPermissionsListener permissionsListener = + new CameraRequestPermissionsListener(fakeResultCallback); + + permissionsListener.onRequestPermissionsResult( + 9796, null, new int[] {PackageManager.PERMISSION_DENIED}); + + verify(fakeResultCallback) + .onResult("CameraAccessDenied", "Camera access permission was denied."); + } + + @Test + public void callback_respondsWithAudioAccessDenied() { + ResultCallback fakeResultCallback = mock(ResultCallback.class); + CameraRequestPermissionsListener permissionsListener = + new CameraRequestPermissionsListener(fakeResultCallback); + + permissionsListener.onRequestPermissionsResult( + 9796, + null, + new int[] {PackageManager.PERMISSION_GRANTED, PackageManager.PERMISSION_DENIED}); + + verify(fakeResultCallback).onResult("AudioAccessDenied", "Audio access permission was denied."); + } + + @Test + public void callback_doesNotRespond() { + ResultCallback fakeResultCallback = mock(ResultCallback.class); + CameraRequestPermissionsListener permissionsListener = + new CameraRequestPermissionsListener(fakeResultCallback); + + permissionsListener.onRequestPermissionsResult( + 9796, + null, + new int[] {PackageManager.PERMISSION_GRANTED, PackageManager.PERMISSION_GRANTED}); + + verify(fakeResultCallback, never()) + .onResult("CameraAccessDenied", "Camera access permission was denied."); + verify(fakeResultCallback, never()) + .onResult("AudioAccessDenied", "Audio access permission was denied."); + } + + @Test + public void callback_respondsWithCameraAccessDeniedWhenEmptyResult() { + // Handles the case where the grantResults array is empty + + ResultCallback fakeResultCallback = mock(ResultCallback.class); + CameraRequestPermissionsListener permissionsListener = + new CameraRequestPermissionsListener(fakeResultCallback); + + permissionsListener.onRequestPermissionsResult(9796, null, new int[] {}); + + verify(fakeResultCallback) + .onResult("CameraAccessDenied", "Camera access permission was denied."); + } +} diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/DeviceOrientationManagerTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/DeviceOrientationManagerTest.java new file mode 100644 index 000000000000..1e2bfba714c7 --- /dev/null +++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/DeviceOrientationManagerTest.java @@ -0,0 +1,313 @@ +// Copyright 2013 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.camerax; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.Activity; +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.provider.Settings; +import android.view.Display; +import android.view.Surface; +import android.view.WindowManager; +import io.flutter.embedding.engine.systemchannels.PlatformChannel.DeviceOrientation; +import io.flutter.plugins.camerax.DeviceOrientationManager.DeviceOrientationChangeCallback; +import org.junit.Before; +import org.junit.Test; +import org.mockito.MockedStatic; + +public class DeviceOrientationManagerTest { + private Activity mockActivity; + private DeviceOrientationChangeCallback mockDeviceOrientationChangeCallback; + private WindowManager mockWindowManager; + private Display mockDisplay; + private DeviceOrientationManager deviceOrientationManager; + + @Before + @SuppressWarnings("deprecation") + public void before() { + mockActivity = mock(Activity.class); + mockDisplay = mock(Display.class); + mockWindowManager = mock(WindowManager.class); + mockDeviceOrientationChangeCallback = mock(DeviceOrientationChangeCallback.class); + + when(mockActivity.getSystemService(Context.WINDOW_SERVICE)).thenReturn(mockWindowManager); + when(mockWindowManager.getDefaultDisplay()).thenReturn(mockDisplay); + + deviceOrientationManager = + new DeviceOrientationManager(mockActivity, false, 0, mockDeviceOrientationChangeCallback); + } + + @Test + public void getVideoOrientation_whenNaturalScreenOrientationEqualsPortraitUp() { + int degreesPortraitUp = + deviceOrientationManager.getVideoOrientation(DeviceOrientation.PORTRAIT_UP); + int degreesPortraitDown = + deviceOrientationManager.getVideoOrientation(DeviceOrientation.PORTRAIT_DOWN); + int degreesLandscapeLeft = + deviceOrientationManager.getVideoOrientation(DeviceOrientation.LANDSCAPE_LEFT); + int degreesLandscapeRight = + deviceOrientationManager.getVideoOrientation(DeviceOrientation.LANDSCAPE_RIGHT); + + assertEquals(0, degreesPortraitUp); + assertEquals(270, degreesLandscapeLeft); + assertEquals(180, degreesPortraitDown); + assertEquals(90, degreesLandscapeRight); + } + + @Test + public void getVideoOrientation_whenNaturalScreenOrientationEqualsLandscapeLeft() { + DeviceOrientationManager orientationManager = + new DeviceOrientationManager(mockActivity, false, 90, mockDeviceOrientationChangeCallback); + + int degreesPortraitUp = orientationManager.getVideoOrientation(DeviceOrientation.PORTRAIT_UP); + int degreesPortraitDown = + orientationManager.getVideoOrientation(DeviceOrientation.PORTRAIT_DOWN); + int degreesLandscapeLeft = + orientationManager.getVideoOrientation(DeviceOrientation.LANDSCAPE_LEFT); + int degreesLandscapeRight = + orientationManager.getVideoOrientation(DeviceOrientation.LANDSCAPE_RIGHT); + + assertEquals(90, degreesPortraitUp); + assertEquals(0, degreesLandscapeLeft); + assertEquals(270, degreesPortraitDown); + assertEquals(180, degreesLandscapeRight); + } + + @Test + public void getVideoOrientation_fallbackToPortraitSensorOrientationWhenOrientationIsNull() { + setUpUIOrientationMocks(Configuration.ORIENTATION_PORTRAIT, Surface.ROTATION_0); + + int degrees = deviceOrientationManager.getVideoOrientation(null); + + assertEquals(0, degrees); + } + + @Test + public void getVideoOrientation_fallbackToLandscapeSensorOrientationWhenOrientationIsNull() { + setUpUIOrientationMocks(Configuration.ORIENTATION_LANDSCAPE, Surface.ROTATION_0); + + DeviceOrientationManager orientationManager = + new DeviceOrientationManager(mockActivity, false, 90, mockDeviceOrientationChangeCallback); + + int degrees = orientationManager.getVideoOrientation(null); + + assertEquals(0, degrees); + } + + @Test + public void getPhotoOrientation_whenNaturalScreenOrientationEqualsPortraitUp() { + int degreesPortraitUp = + deviceOrientationManager.getPhotoOrientation(DeviceOrientation.PORTRAIT_UP); + int degreesPortraitDown = + deviceOrientationManager.getPhotoOrientation(DeviceOrientation.PORTRAIT_DOWN); + int degreesLandscapeLeft = + deviceOrientationManager.getPhotoOrientation(DeviceOrientation.LANDSCAPE_LEFT); + int degreesLandscapeRight = + deviceOrientationManager.getPhotoOrientation(DeviceOrientation.LANDSCAPE_RIGHT); + + assertEquals(0, degreesPortraitUp); + assertEquals(90, degreesLandscapeRight); + assertEquals(180, degreesPortraitDown); + assertEquals(270, degreesLandscapeLeft); + } + + @Test + public void getPhotoOrientation_whenNaturalScreenOrientationEqualsLandscapeLeft() { + DeviceOrientationManager orientationManager = + new DeviceOrientationManager(mockActivity, false, 90, mockDeviceOrientationChangeCallback); + + int degreesPortraitUp = orientationManager.getPhotoOrientation(DeviceOrientation.PORTRAIT_UP); + int degreesPortraitDown = + orientationManager.getPhotoOrientation(DeviceOrientation.PORTRAIT_DOWN); + int degreesLandscapeLeft = + orientationManager.getPhotoOrientation(DeviceOrientation.LANDSCAPE_LEFT); + int degreesLandscapeRight = + orientationManager.getPhotoOrientation(DeviceOrientation.LANDSCAPE_RIGHT); + + assertEquals(90, degreesPortraitUp); + assertEquals(180, degreesLandscapeRight); + assertEquals(270, degreesPortraitDown); + assertEquals(0, degreesLandscapeLeft); + } + + @Test + public void getPhotoOrientation_shouldFallbackToCurrentOrientationWhenOrientationIsNull() { + setUpUIOrientationMocks(Configuration.ORIENTATION_LANDSCAPE, Surface.ROTATION_0); + + int degrees = deviceOrientationManager.getPhotoOrientation(null); + + assertEquals(270, degrees); + } + + @Test + public void handleUIOrientationChange_shouldSendMessageWhenSensorAccessIsAllowed() { + try (MockedStatic mockedSystem = mockStatic(Settings.System.class)) { + mockedSystem + .when( + () -> + Settings.System.getInt(any(), eq(Settings.System.ACCELEROMETER_ROTATION), eq(0))) + .thenReturn(0); + setUpUIOrientationMocks(Configuration.ORIENTATION_LANDSCAPE, Surface.ROTATION_0); + + deviceOrientationManager.handleUIOrientationChange(); + } + + verify(mockDeviceOrientationChangeCallback, times(1)) + .onChange(DeviceOrientation.LANDSCAPE_LEFT); + } + + @Test + public void handleOrientationChange_shouldSendMessageWhenOrientationIsUpdated() { + DeviceOrientation previousOrientation = DeviceOrientation.PORTRAIT_UP; + DeviceOrientation newOrientation = DeviceOrientation.LANDSCAPE_LEFT; + + DeviceOrientationManager.handleOrientationChange( + newOrientation, previousOrientation, mockDeviceOrientationChangeCallback); + + verify(mockDeviceOrientationChangeCallback, times(1)).onChange(newOrientation); + } + + @Test + public void handleOrientationChange_shouldNotSendMessageWhenOrientationIsNotUpdated() { + DeviceOrientation previousOrientation = DeviceOrientation.PORTRAIT_UP; + DeviceOrientation newOrientation = DeviceOrientation.PORTRAIT_UP; + + DeviceOrientationManager.handleOrientationChange( + newOrientation, previousOrientation, mockDeviceOrientationChangeCallback); + + verify(mockDeviceOrientationChangeCallback, never()).onChange(any()); + } + + @Test + public void getUIOrientation() { + // Orientation portrait and rotation of 0 should translate to "PORTRAIT_UP". + setUpUIOrientationMocks(Configuration.ORIENTATION_PORTRAIT, Surface.ROTATION_0); + DeviceOrientation uiOrientation = deviceOrientationManager.getUIOrientation(); + assertEquals(DeviceOrientation.PORTRAIT_UP, uiOrientation); + + // Orientation portrait and rotation of 90 should translate to "PORTRAIT_UP". + setUpUIOrientationMocks(Configuration.ORIENTATION_PORTRAIT, Surface.ROTATION_90); + uiOrientation = deviceOrientationManager.getUIOrientation(); + assertEquals(DeviceOrientation.PORTRAIT_UP, uiOrientation); + + // Orientation portrait and rotation of 180 should translate to "PORTRAIT_DOWN". + setUpUIOrientationMocks(Configuration.ORIENTATION_PORTRAIT, Surface.ROTATION_180); + uiOrientation = deviceOrientationManager.getUIOrientation(); + assertEquals(DeviceOrientation.PORTRAIT_DOWN, uiOrientation); + + // Orientation portrait and rotation of 270 should translate to "PORTRAIT_DOWN". + setUpUIOrientationMocks(Configuration.ORIENTATION_PORTRAIT, Surface.ROTATION_270); + uiOrientation = deviceOrientationManager.getUIOrientation(); + assertEquals(DeviceOrientation.PORTRAIT_DOWN, uiOrientation); + + // Orientation landscape and rotation of 0 should translate to "LANDSCAPE_LEFT". + setUpUIOrientationMocks(Configuration.ORIENTATION_LANDSCAPE, Surface.ROTATION_0); + uiOrientation = deviceOrientationManager.getUIOrientation(); + assertEquals(DeviceOrientation.LANDSCAPE_LEFT, uiOrientation); + + // Orientation landscape and rotation of 90 should translate to "LANDSCAPE_LEFT". + setUpUIOrientationMocks(Configuration.ORIENTATION_LANDSCAPE, Surface.ROTATION_90); + uiOrientation = deviceOrientationManager.getUIOrientation(); + assertEquals(DeviceOrientation.LANDSCAPE_LEFT, uiOrientation); + + // Orientation landscape and rotation of 180 should translate to "LANDSCAPE_RIGHT". + setUpUIOrientationMocks(Configuration.ORIENTATION_LANDSCAPE, Surface.ROTATION_180); + uiOrientation = deviceOrientationManager.getUIOrientation(); + assertEquals(DeviceOrientation.LANDSCAPE_RIGHT, uiOrientation); + + // Orientation landscape and rotation of 270 should translate to "LANDSCAPE_RIGHT". + setUpUIOrientationMocks(Configuration.ORIENTATION_LANDSCAPE, Surface.ROTATION_270); + uiOrientation = deviceOrientationManager.getUIOrientation(); + assertEquals(DeviceOrientation.LANDSCAPE_RIGHT, uiOrientation); + + // Orientation undefined should default to "PORTRAIT_UP". + setUpUIOrientationMocks(Configuration.ORIENTATION_UNDEFINED, Surface.ROTATION_0); + uiOrientation = deviceOrientationManager.getUIOrientation(); + assertEquals(DeviceOrientation.PORTRAIT_UP, uiOrientation); + } + + @Test + public void getDeviceDefaultOrientation() { + setUpUIOrientationMocks(Configuration.ORIENTATION_PORTRAIT, Surface.ROTATION_0); + int orientation = deviceOrientationManager.getDeviceDefaultOrientation(); + assertEquals(Configuration.ORIENTATION_PORTRAIT, orientation); + + setUpUIOrientationMocks(Configuration.ORIENTATION_PORTRAIT, Surface.ROTATION_180); + orientation = deviceOrientationManager.getDeviceDefaultOrientation(); + assertEquals(Configuration.ORIENTATION_PORTRAIT, orientation); + + setUpUIOrientationMocks(Configuration.ORIENTATION_PORTRAIT, Surface.ROTATION_90); + orientation = deviceOrientationManager.getDeviceDefaultOrientation(); + assertEquals(Configuration.ORIENTATION_LANDSCAPE, orientation); + + setUpUIOrientationMocks(Configuration.ORIENTATION_PORTRAIT, Surface.ROTATION_270); + orientation = deviceOrientationManager.getDeviceDefaultOrientation(); + assertEquals(Configuration.ORIENTATION_LANDSCAPE, orientation); + + setUpUIOrientationMocks(Configuration.ORIENTATION_LANDSCAPE, Surface.ROTATION_0); + orientation = deviceOrientationManager.getDeviceDefaultOrientation(); + assertEquals(Configuration.ORIENTATION_LANDSCAPE, orientation); + + setUpUIOrientationMocks(Configuration.ORIENTATION_LANDSCAPE, Surface.ROTATION_180); + orientation = deviceOrientationManager.getDeviceDefaultOrientation(); + assertEquals(Configuration.ORIENTATION_LANDSCAPE, orientation); + + setUpUIOrientationMocks(Configuration.ORIENTATION_LANDSCAPE, Surface.ROTATION_90); + orientation = deviceOrientationManager.getDeviceDefaultOrientation(); + assertEquals(Configuration.ORIENTATION_PORTRAIT, orientation); + + setUpUIOrientationMocks(Configuration.ORIENTATION_LANDSCAPE, Surface.ROTATION_270); + orientation = deviceOrientationManager.getDeviceDefaultOrientation(); + assertEquals(Configuration.ORIENTATION_PORTRAIT, orientation); + } + + @Test + public void calculateSensorOrientation() { + setUpUIOrientationMocks(Configuration.ORIENTATION_PORTRAIT, Surface.ROTATION_0); + DeviceOrientation orientation = deviceOrientationManager.calculateSensorOrientation(0); + assertEquals(DeviceOrientation.PORTRAIT_UP, orientation); + + setUpUIOrientationMocks(Configuration.ORIENTATION_PORTRAIT, Surface.ROTATION_0); + orientation = deviceOrientationManager.calculateSensorOrientation(90); + assertEquals(DeviceOrientation.LANDSCAPE_LEFT, orientation); + + setUpUIOrientationMocks(Configuration.ORIENTATION_PORTRAIT, Surface.ROTATION_0); + orientation = deviceOrientationManager.calculateSensorOrientation(180); + assertEquals(DeviceOrientation.PORTRAIT_DOWN, orientation); + + setUpUIOrientationMocks(Configuration.ORIENTATION_PORTRAIT, Surface.ROTATION_0); + orientation = deviceOrientationManager.calculateSensorOrientation(270); + assertEquals(DeviceOrientation.LANDSCAPE_RIGHT, orientation); + } + + private void setUpUIOrientationMocks(int orientation, int rotation) { + Resources mockResources = mock(Resources.class); + Configuration mockConfiguration = mock(Configuration.class); + + when(mockDisplay.getRotation()).thenReturn(rotation); + + mockConfiguration.orientation = orientation; + when(mockActivity.getResources()).thenReturn(mockResources); + when(mockResources.getConfiguration()).thenReturn(mockConfiguration); + } + + @Test + public void getDisplayTest() { + Display display = deviceOrientationManager.getDisplay(); + + assertEquals(mockDisplay, display); + } +} diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/SystemServicesTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/SystemServicesTest.java new file mode 100644 index 000000000000..d90c2633271c --- /dev/null +++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/SystemServicesTest.java @@ -0,0 +1,137 @@ +// Copyright 2013 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.camerax; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.Activity; +import io.flutter.embedding.engine.systemchannels.PlatformChannel.DeviceOrientation; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugins.camerax.CameraPermissionsManager.PermissionsRegistry; +import io.flutter.plugins.camerax.CameraPermissionsManager.ResultCallback; +import io.flutter.plugins.camerax.DeviceOrientationManager.DeviceOrientationChangeCallback; +import io.flutter.plugins.camerax.GeneratedCameraXLibrary.CameraPermissionsErrorData; +import io.flutter.plugins.camerax.GeneratedCameraXLibrary.Result; +import io.flutter.plugins.camerax.GeneratedCameraXLibrary.SystemServicesFlutterApi.Reply; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +public class SystemServicesTest { + @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); + + @Mock public BinaryMessenger mockBinaryMessenger; + @Mock public InstanceManager mockInstanceManager; + + @Test + public void requestCameraPermissionsTest() { + final SystemServicesHostApiImpl systemServicesHostApi = + new SystemServicesHostApiImpl(mockBinaryMessenger, mockInstanceManager); + final CameraXProxy mockCameraXProxy = mock(CameraXProxy.class); + final CameraPermissionsManager mockCameraPermissionsManager = + mock(CameraPermissionsManager.class); + final Activity mockActivity = mock(Activity.class); + final PermissionsRegistry mockPermissionsRegistry = mock(PermissionsRegistry.class); + final Result mockResult = mock(Result.class); + final Boolean enableAudio = false; + + systemServicesHostApi.cameraXProxy = mockCameraXProxy; + systemServicesHostApi.setActivity(mockActivity); + systemServicesHostApi.setPermissionsRegistry(mockPermissionsRegistry); + when(mockCameraXProxy.createCameraPermissionsManager()) + .thenReturn(mockCameraPermissionsManager); + + final ArgumentCaptor resultCallbackCaptor = + ArgumentCaptor.forClass(ResultCallback.class); + + systemServicesHostApi.requestCameraPermissions(enableAudio, mockResult); + + // Test camera permissions are requested. + verify(mockCameraPermissionsManager) + .requestPermissions( + eq(mockActivity), + eq(mockPermissionsRegistry), + eq(enableAudio), + resultCallbackCaptor.capture()); + + ResultCallback resultCallback = (ResultCallback) resultCallbackCaptor.getValue(); + + // Test no error data is sent upon permissions request success. + resultCallback.onResult(null, null); + verify(mockResult).success(null); + + // Test expected error data is sent upon permissions request failure. + final String testErrorCode = "TestErrorCode"; + final String testErrorDescription = "Test error description."; + + final ArgumentCaptor cameraPermissionsErrorDataCaptor = + ArgumentCaptor.forClass(CameraPermissionsErrorData.class); + + resultCallback.onResult(testErrorCode, testErrorDescription); + verify(mockResult, times(2)).success(cameraPermissionsErrorDataCaptor.capture()); + + CameraPermissionsErrorData cameraPermissionsErrorData = + cameraPermissionsErrorDataCaptor.getValue(); + assertEquals(cameraPermissionsErrorData.getErrorCode(), testErrorCode); + assertEquals(cameraPermissionsErrorData.getDescription(), testErrorDescription); + } + + @Test + public void deviceOrientationChangeTest() { + final SystemServicesHostApiImpl systemServicesHostApi = + new SystemServicesHostApiImpl(mockBinaryMessenger, mockInstanceManager); + final CameraXProxy mockCameraXProxy = mock(CameraXProxy.class); + final Activity mockActivity = mock(Activity.class); + final DeviceOrientationManager mockDeviceOrientationManager = + mock(DeviceOrientationManager.class); + final Boolean isFrontFacing = true; + final int sensorOrientation = 90; + + SystemServicesFlutterApiImpl systemServicesFlutterApi = + mock(SystemServicesFlutterApiImpl.class); + systemServicesHostApi.systemServicesFlutterApi = systemServicesFlutterApi; + + systemServicesHostApi.cameraXProxy = mockCameraXProxy; + systemServicesHostApi.setActivity(mockActivity); + when(mockCameraXProxy.createDeviceOrientationManager( + eq(mockActivity), + eq(isFrontFacing), + eq(sensorOrientation), + any(DeviceOrientationChangeCallback.class))) + .thenReturn(mockDeviceOrientationManager); + + final ArgumentCaptor deviceOrientationChangeCallbackCaptor = + ArgumentCaptor.forClass(DeviceOrientationChangeCallback.class); + + systemServicesHostApi.startListeningForDeviceOrientationChange( + isFrontFacing, Long.valueOf(sensorOrientation)); + + // Test callback method defined in Flutter API is called when device orientation changes. + verify(mockCameraXProxy) + .createDeviceOrientationManager( + eq(mockActivity), + eq(isFrontFacing), + eq(sensorOrientation), + deviceOrientationChangeCallbackCaptor.capture()); + DeviceOrientationChangeCallback deviceOrientationChangeCallback = + deviceOrientationChangeCallbackCaptor.getValue(); + + deviceOrientationChangeCallback.onChange(DeviceOrientation.PORTRAIT_DOWN); + verify(systemServicesFlutterApi) + .onDeviceOrientationChanged(eq("PORTRAIT_DOWN"), any(Reply.class)); + + // Test that the DeviceOrientationManager starts listening for device orientation changes. + verify(mockDeviceOrientationManager).start(); + } +} diff --git a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax_flutter_api_impls.dart b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax_flutter_api_impls.dart index 620831bfe54d..2895a38e4132 100644 --- a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax_flutter_api_impls.dart +++ b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax_flutter_api_impls.dart @@ -8,6 +8,7 @@ import 'camera_selector.dart'; import 'camerax_library.pigeon.dart'; import 'java_object.dart'; import 'process_camera_provider.dart'; +import 'system_services.dart'; /// Handles initialization of Flutter APIs for the Android CameraX library. class AndroidCameraXCameraFlutterApis { @@ -18,6 +19,7 @@ class AndroidCameraXCameraFlutterApis { CameraInfoFlutterApiImpl? cameraInfoFlutterApi, CameraSelectorFlutterApiImpl? cameraSelectorFlutterApi, ProcessCameraProviderFlutterApiImpl? processCameraProviderFlutterApi, + SystemServicesFlutterApiImpl? systemServicesFlutterApi, }) { this.javaObjectFlutterApi = javaObjectFlutterApi ?? JavaObjectFlutterApiImpl(); @@ -28,6 +30,8 @@ class AndroidCameraXCameraFlutterApis { this.processCameraProviderFlutterApi = processCameraProviderFlutterApi ?? ProcessCameraProviderFlutterApiImpl(); this.cameraFlutterApi = cameraFlutterApi ?? CameraFlutterApiImpl(); + this.systemServicesFlutterApi = + systemServicesFlutterApi ?? SystemServicesFlutterApiImpl(); } static bool _haveBeenSetUp = false; @@ -54,6 +58,9 @@ class AndroidCameraXCameraFlutterApis { /// Flutter Api for [Camera]. late final CameraFlutterApiImpl cameraFlutterApi; + /// Flutter Api for [SystemServices]. + late final SystemServicesFlutterApiImpl systemServicesFlutterApi; + /// Ensures all the Flutter APIs have been setup to receive calls from native code. void ensureSetUp() { if (!_haveBeenSetUp) { @@ -62,6 +69,7 @@ class AndroidCameraXCameraFlutterApis { CameraSelectorFlutterApi.setup(cameraSelectorFlutterApi); ProcessCameraProviderFlutterApi.setup(processCameraProviderFlutterApi); CameraFlutterApi.setup(cameraFlutterApi); + SystemServicesFlutterApi.setup(systemServicesFlutterApi); _haveBeenSetUp = true; } } diff --git a/packages/camera/camera_android_camerax/lib/src/camerax_library.pigeon.dart b/packages/camera/camera_android_camerax/lib/src/camerax_library.pigeon.dart index 636a375145b9..6d8869968f41 100644 --- a/packages/camera/camera_android_camerax/lib/src/camerax_library.pigeon.dart +++ b/packages/camera/camera_android_camerax/lib/src/camerax_library.pigeon.dart @@ -10,6 +10,31 @@ import 'dart:typed_data' show Uint8List, Int32List, Int64List, Float64List; import 'package:flutter/foundation.dart' show WriteBuffer, ReadBuffer; import 'package:flutter/services.dart'; +class CameraPermissionsErrorData { + CameraPermissionsErrorData({ + required this.errorCode, + required this.description, + }); + + String errorCode; + String description; + + Object encode() { + final Map pigeonMap = {}; + pigeonMap['errorCode'] = errorCode; + pigeonMap['description'] = description; + return pigeonMap; + } + + static CameraPermissionsErrorData decode(Object message) { + final Map pigeonMap = message as Map; + return CameraPermissionsErrorData( + errorCode: pigeonMap['errorCode']! as String, + description: pigeonMap['description']! as String, + ); + } +} + class _JavaObjectHostApiCodec extends StandardMessageCodec { const _JavaObjectHostApiCodec(); } @@ -486,3 +511,150 @@ abstract class CameraFlutterApi { } } } + +class _SystemServicesHostApiCodec extends StandardMessageCodec { + const _SystemServicesHostApiCodec(); + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is CameraPermissionsErrorData) { + buffer.putUint8(128); + writeValue(buffer, value.encode()); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + case 128: + return CameraPermissionsErrorData.decode(readValue(buffer)!); + + default: + return super.readValueOfType(type, buffer); + } + } +} + +class SystemServicesHostApi { + /// Constructor for [SystemServicesHostApi]. The [binaryMessenger] named argument is + /// available for dependency injection. If it is left null, the default + /// BinaryMessenger will be used which routes to the host platform. + SystemServicesHostApi({BinaryMessenger? binaryMessenger}) + : _binaryMessenger = binaryMessenger; + + final BinaryMessenger? _binaryMessenger; + + static const MessageCodec codec = _SystemServicesHostApiCodec(); + + Future requestCameraPermissions( + bool arg_enableAudio) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.SystemServicesHostApi.requestCameraPermissions', + codec, + binaryMessenger: _binaryMessenger); + final Map? replyMap = await channel + .send([arg_enableAudio]) as Map?; + if (replyMap == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyMap['error'] != null) { + final Map error = + (replyMap['error'] as Map?)!; + throw PlatformException( + code: (error['code'] as String?)!, + message: error['message'] as String?, + details: error['details'], + ); + } else { + return (replyMap['result'] as CameraPermissionsErrorData?); + } + } + + Future startListeningForDeviceOrientationChange( + bool arg_isFrontFacing, int arg_sensorOrientation) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.SystemServicesHostApi.startListeningForDeviceOrientationChange', + codec, + binaryMessenger: _binaryMessenger); + final Map? replyMap = + await channel.send([arg_isFrontFacing, arg_sensorOrientation]) + as Map?; + if (replyMap == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyMap['error'] != null) { + final Map error = + (replyMap['error'] as Map?)!; + throw PlatformException( + code: (error['code'] as String?)!, + message: error['message'] as String?, + details: error['details'], + ); + } else { + return; + } + } + + Future stopListeningForDeviceOrientationChange() async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.SystemServicesHostApi.stopListeningForDeviceOrientationChange', + codec, + binaryMessenger: _binaryMessenger); + final Map? replyMap = + await channel.send(null) as Map?; + if (replyMap == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyMap['error'] != null) { + final Map error = + (replyMap['error'] as Map?)!; + throw PlatformException( + code: (error['code'] as String?)!, + message: error['message'] as String?, + details: error['details'], + ); + } else { + return; + } + } +} + +class _SystemServicesFlutterApiCodec extends StandardMessageCodec { + const _SystemServicesFlutterApiCodec(); +} + +abstract class SystemServicesFlutterApi { + static const MessageCodec codec = _SystemServicesFlutterApiCodec(); + + void onDeviceOrientationChanged(String orientation); + static void setup(SystemServicesFlutterApi? api, + {BinaryMessenger? binaryMessenger}) { + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.SystemServicesFlutterApi.onDeviceOrientationChanged', + codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMessageHandler(null); + } else { + channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.SystemServicesFlutterApi.onDeviceOrientationChanged was null.'); + final List args = (message as List?)!; + final String? arg_orientation = (args[0] as String?); + assert(arg_orientation != null, + 'Argument for dev.flutter.pigeon.SystemServicesFlutterApi.onDeviceOrientationChanged was null, expected non-null String.'); + api.onDeviceOrientationChanged(arg_orientation!); + return; + }); + } + } + } +} diff --git a/packages/camera/camera_android_camerax/lib/src/system_services.dart b/packages/camera/camera_android_camerax/lib/src/system_services.dart new file mode 100644 index 000000000000..d5fbe5a92c91 --- /dev/null +++ b/packages/camera/camera_android_camerax/lib/src/system_services.dart @@ -0,0 +1,137 @@ +// Copyright 2013 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. + +import 'dart:async'; + +import 'package:camera_platform_interface/camera_platform_interface.dart' + show CameraException, DeviceOrientationChangedEvent; +import 'package:flutter/services.dart'; + +import 'android_camera_camerax_flutter_api_impls.dart'; +import 'camerax_library.pigeon.dart'; + +// Ignoring lint indicating this class only contains static members +// as this class is a wrapper for various Android system services. +// ignore_for_file: avoid_classes_with_only_static_members + +/// Utility class that offers access to Android system services needed for +/// camera usage. +class SystemServices { + /// Stream that emits the device orientation whenever it is changed. + /// + /// Values may start being added to the stream once + /// `startListeningForDeviceOrientationChange(...)` is called. + static final StreamController + deviceOrientationChangedStreamController = + StreamController.broadcast(); + + /// Requests permission to access the camera and audio if specified. + static Future requestCameraPermissions(bool enableAudio, + {BinaryMessenger? binaryMessenger}) { + final SystemServicesHostApiImpl api = + SystemServicesHostApiImpl(binaryMessenger: binaryMessenger); + + return api.sendCameraPermissionsRequest(enableAudio); + } + + /// Requests that [deviceOrientationChangedStreamController] start + /// emitting values for any change in device orientation. + static void startListeningForDeviceOrientationChange( + bool isFrontFacing, int sensorOrientation, + {BinaryMessenger? binaryMessenger}) { + AndroidCameraXCameraFlutterApis.instance.ensureSetUp(); + final SystemServicesHostApi api = + SystemServicesHostApi(binaryMessenger: binaryMessenger); + + api.startListeningForDeviceOrientationChange( + isFrontFacing, sensorOrientation); + } + + /// Stops the [deviceOrientationChangedStreamController] from emitting values + /// for changes in device orientation. + static void stopListeningForDeviceOrientationChange( + {BinaryMessenger? binaryMessenger}) { + final SystemServicesHostApi api = + SystemServicesHostApi(binaryMessenger: binaryMessenger); + + api.stopListeningForDeviceOrientationChange(); + } +} + +/// Host API implementation of [SystemServices]. +class SystemServicesHostApiImpl extends SystemServicesHostApi { + /// Creates a [SystemServicesHostApiImpl]. + SystemServicesHostApiImpl({this.binaryMessenger}) + : super(binaryMessenger: binaryMessenger); + + /// Receives binary data across the Flutter platform barrier. + /// + /// If it is null, the default BinaryMessenger will be used which routes to + /// the host platform. + final BinaryMessenger? binaryMessenger; + + /// Requests permission to access the camera and audio if specified. + /// + /// Will complete normally if permissions are successfully granted; otherwise, + /// will throw a [CameraException]. + Future sendCameraPermissionsRequest(bool enableAudio) async { + final CameraPermissionsErrorData? error = + await requestCameraPermissions(enableAudio); + + if (error != null) { + throw CameraException( + error.errorCode, + error.description, + ); + } + } +} + +/// Flutter API implementation of [SystemServices]. +class SystemServicesFlutterApiImpl implements SystemServicesFlutterApi { + /// Constructs a [SystemServicesFlutterApiImpl]. + SystemServicesFlutterApiImpl({ + this.binaryMessenger, + }); + + /// Receives binary data across the Flutter platform barrier. + /// + /// If it is null, the default BinaryMessenger will be used which routes to + /// the host platform. + final BinaryMessenger? binaryMessenger; + + /// Callback method for any changes in device orientation. + /// + /// Will only be called if + /// `SystemServices.startListeningForDeviceOrientationChange(...)` was called + /// to start listening for device orientation updates. + @override + void onDeviceOrientationChanged(String orientation) { + final DeviceOrientation deviceOrientation = + deserializeDeviceOrientation(orientation); + if (deviceOrientation == null) { + return; + } + SystemServices.deviceOrientationChangedStreamController + .add(DeviceOrientationChangedEvent(deviceOrientation)); + } + + /// Deserializes device orientation in [String] format into a + /// [DeviceOrientation]. + DeviceOrientation deserializeDeviceOrientation(String orientation) { + switch (orientation) { + case 'LANDSCAPE_LEFT': + return DeviceOrientation.landscapeLeft; + case 'LANDSCAPE_RIGHT': + return DeviceOrientation.landscapeRight; + case 'PORTRAIT_DOWN': + return DeviceOrientation.portraitDown; + case 'PORTRAIT_UP': + return DeviceOrientation.portraitUp; + default: + throw ArgumentError( + '"$orientation" is not a valid DeviceOrientation value'); + } + } +} diff --git a/packages/camera/camera_android_camerax/pigeons/camerax_library.dart b/packages/camera/camera_android_camerax/pigeons/camerax_library.dart index edd2059e162b..5e804a225eda 100644 --- a/packages/camera/camera_android_camerax/pigeons/camerax_library.dart +++ b/packages/camera/camera_android_camerax/pigeons/camerax_library.dart @@ -26,6 +26,16 @@ import 'package:pigeon/pigeon.dart'; ), ), ) +class CameraPermissionsErrorData { + CameraPermissionsErrorData({ + required this.errorCode, + required this.description, + }); + + String errorCode; + String description; +} + @HostApi(dartHostTestHandler: 'TestJavaObjectHostApi') abstract class JavaObjectHostApi { void dispose(int identifier); @@ -82,3 +92,19 @@ abstract class ProcessCameraProviderFlutterApi { abstract class CameraFlutterApi { void create(int identifier); } + +@HostApi(dartHostTestHandler: 'TestSystemServicesHostApi') +abstract class SystemServicesHostApi { + @async + CameraPermissionsErrorData? requestCameraPermissions(bool enableAudio); + + void startListeningForDeviceOrientationChange( + bool isFrontFacing, int sensorOrientation); + + void stopListeningForDeviceOrientationChange(); +} + +@FlutterApi() +abstract class SystemServicesFlutterApi { + void onDeviceOrientationChanged(String orientation); +} diff --git a/packages/camera/camera_android_camerax/pubspec.yaml b/packages/camera/camera_android_camerax/pubspec.yaml index 9873db1a0121..7f81ecbd4f71 100644 --- a/packages/camera/camera_android_camerax/pubspec.yaml +++ b/packages/camera/camera_android_camerax/pubspec.yaml @@ -21,6 +21,7 @@ dependencies: camera_platform_interface: ^2.2.0 flutter: sdk: flutter + stream_transform: ^2.1.0 dev_dependencies: build_runner: ^2.1.4 diff --git a/packages/camera/camera_android_camerax/test/system_services_test.dart b/packages/camera/camera_android_camerax/test/system_services_test.dart new file mode 100644 index 000000000000..c8a2721ecc2d --- /dev/null +++ b/packages/camera/camera_android_camerax/test/system_services_test.dart @@ -0,0 +1,101 @@ +// Copyright 2013 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. + +import 'package:camera_android_camerax/src/camerax_library.pigeon.dart' + show CameraPermissionsErrorData; +import 'package:camera_android_camerax/src/system_services.dart'; +import 'package:camera_platform_interface/camera_platform_interface.dart' + show CameraException, DeviceOrientationChangedEvent; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; + +import 'system_services_test.mocks.dart'; +import 'test_camerax_library.pigeon.dart'; + +@GenerateMocks([TestSystemServicesHostApi]) +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('SystemServices', () { + tearDown(() => TestProcessCameraProviderHostApi.setup(null)); + + test( + 'requestCameraPermissionsFromInstance completes normally without errors test', + () async { + final MockTestSystemServicesHostApi mockApi = + MockTestSystemServicesHostApi(); + TestSystemServicesHostApi.setup(mockApi); + + when(mockApi.requestCameraPermissions(true)) + .thenAnswer((_) async => null); + + await SystemServices.requestCameraPermissions(true); + verify(mockApi.requestCameraPermissions(true)); + }); + + test( + 'requestCameraPermissionsFromInstance throws CameraException if there was a request error', + () { + final MockTestSystemServicesHostApi mockApi = + MockTestSystemServicesHostApi(); + TestSystemServicesHostApi.setup(mockApi); + final CameraPermissionsErrorData error = CameraPermissionsErrorData( + errorCode: 'Test error code', + description: 'Test error description', + ); + + when(mockApi.requestCameraPermissions(true)) + .thenAnswer((_) async => error); + + expect( + () async => SystemServices.requestCameraPermissions(true), + throwsA(isA() + .having((CameraException e) => e.code, 'code', 'Test error code') + .having((CameraException e) => e.description, 'description', + 'Test error description'))); + verify(mockApi.requestCameraPermissions(true)); + }); + + test('startListeningForDeviceOrientationChangeTest', () async { + final MockTestSystemServicesHostApi mockApi = + MockTestSystemServicesHostApi(); + TestSystemServicesHostApi.setup(mockApi); + + SystemServices.startListeningForDeviceOrientationChange(true, 90); + verify(mockApi.startListeningForDeviceOrientationChange(true, 90)); + }); + + test('stopListeningForDeviceOrientationChangeTest', () async { + final MockTestSystemServicesHostApi mockApi = + MockTestSystemServicesHostApi(); + TestSystemServicesHostApi.setup(mockApi); + + SystemServices.stopListeningForDeviceOrientationChange(); + verify(mockApi.stopListeningForDeviceOrientationChange()); + }); + + test('onDeviceOrientationChanged adds new orientation to stream', () { + SystemServices.deviceOrientationChangedStreamController.stream + .listen((DeviceOrientationChangedEvent event) { + expect(event.orientation, equals(DeviceOrientation.landscapeLeft)); + }); + SystemServicesFlutterApiImpl() + .onDeviceOrientationChanged('LANDSCAPE_LEFT'); + }); + + test( + 'onDeviceOrientationChanged throws error if new orientation is invalid', + () { + expect( + () => SystemServicesFlutterApiImpl() + .onDeviceOrientationChanged('FAKE_ORIENTATION'), + throwsA(isA().having( + (ArgumentError e) => e.message, + 'message', + '"FAKE_ORIENTATION" is not a valid DeviceOrientation value'))); + }); + }); +} diff --git a/packages/camera/camera_android_camerax/test/system_services_test.mocks.dart b/packages/camera/camera_android_camerax/test/system_services_test.mocks.dart new file mode 100644 index 000000000000..2698d2d52307 --- /dev/null +++ b/packages/camera/camera_android_camerax/test/system_services_test.mocks.dart @@ -0,0 +1,66 @@ +// Mocks generated by Mockito 5.3.2 from annotations +// in camera_android_camerax/test/system_services_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i3; + +import 'package:camera_android_camerax/src/camerax_library.pigeon.dart' as _i4; +import 'package:mockito/mockito.dart' as _i1; + +import 'test_camerax_library.pigeon.dart' as _i2; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +/// A class which mocks [TestSystemServicesHostApi]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockTestSystemServicesHostApi extends _i1.Mock + implements _i2.TestSystemServicesHostApi { + MockTestSystemServicesHostApi() { + _i1.throwOnMissingStub(this); + } + + @override + _i3.Future<_i4.CameraPermissionsErrorData?> requestCameraPermissions( + bool? enableAudio) => + (super.noSuchMethod( + Invocation.method( + #requestCameraPermissions, + [enableAudio], + ), + returnValue: _i3.Future<_i4.CameraPermissionsErrorData?>.value(), + ) as _i3.Future<_i4.CameraPermissionsErrorData?>); + @override + void startListeningForDeviceOrientationChange( + bool? isFrontFacing, + int? sensorOrientation, + ) => + super.noSuchMethod( + Invocation.method( + #startListeningForDeviceOrientationChange, + [ + isFrontFacing, + sensorOrientation, + ], + ), + returnValueForMissingStub: null, + ); + @override + void stopListeningForDeviceOrientationChange() => super.noSuchMethod( + Invocation.method( + #stopListeningForDeviceOrientationChange, + [], + ), + returnValueForMissingStub: null, + ); +} diff --git a/packages/camera/camera_android_camerax/test/test_camerax_library.pigeon.dart b/packages/camera/camera_android_camerax/test/test_camerax_library.pigeon.dart index c6afe067a3b6..349004c7e6de 100644 --- a/packages/camera/camera_android_camerax/test/test_camerax_library.pigeon.dart +++ b/packages/camera/camera_android_camerax/test/test_camerax_library.pigeon.dart @@ -259,3 +259,100 @@ abstract class TestProcessCameraProviderHostApi { } } } + +class _TestSystemServicesHostApiCodec extends StandardMessageCodec { + const _TestSystemServicesHostApiCodec(); + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is CameraPermissionsErrorData) { + buffer.putUint8(128); + writeValue(buffer, value.encode()); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + case 128: + return CameraPermissionsErrorData.decode(readValue(buffer)!); + + default: + return super.readValueOfType(type, buffer); + } + } +} + +abstract class TestSystemServicesHostApi { + static const MessageCodec codec = _TestSystemServicesHostApiCodec(); + + Future requestCameraPermissions( + bool enableAudio); + void startListeningForDeviceOrientationChange( + bool isFrontFacing, int sensorOrientation); + void stopListeningForDeviceOrientationChange(); + static void setup(TestSystemServicesHostApi? api, + {BinaryMessenger? binaryMessenger}) { + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.SystemServicesHostApi.requestCameraPermissions', + codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMockMessageHandler(null); + } else { + channel.setMockMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.SystemServicesHostApi.requestCameraPermissions was null.'); + final List args = (message as List?)!; + final bool? arg_enableAudio = (args[0] as bool?); + assert(arg_enableAudio != null, + 'Argument for dev.flutter.pigeon.SystemServicesHostApi.requestCameraPermissions was null, expected non-null bool.'); + final CameraPermissionsErrorData? output = + await api.requestCameraPermissions(arg_enableAudio!); + return {'result': output}; + }); + } + } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.SystemServicesHostApi.startListeningForDeviceOrientationChange', + codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMockMessageHandler(null); + } else { + channel.setMockMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.SystemServicesHostApi.startListeningForDeviceOrientationChange was null.'); + final List args = (message as List?)!; + final bool? arg_isFrontFacing = (args[0] as bool?); + assert(arg_isFrontFacing != null, + 'Argument for dev.flutter.pigeon.SystemServicesHostApi.startListeningForDeviceOrientationChange was null, expected non-null bool.'); + final int? arg_sensorOrientation = (args[1] as int?); + assert(arg_sensorOrientation != null, + 'Argument for dev.flutter.pigeon.SystemServicesHostApi.startListeningForDeviceOrientationChange was null, expected non-null int.'); + api.startListeningForDeviceOrientationChange( + arg_isFrontFacing!, arg_sensorOrientation!); + return {}; + }); + } + } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.SystemServicesHostApi.stopListeningForDeviceOrientationChange', + codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMockMessageHandler(null); + } else { + channel.setMockMessageHandler((Object? message) async { + // ignore message + api.stopListeningForDeviceOrientationChange(); + return {}; + }); + } + } + } +} From 5dd0f41a281f6d29ae5d2c4bf738232c7689a3e8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 31 Jan 2023 03:27:53 +0000 Subject: [PATCH 051/130] [webview]: Bump mockito-inline (#7056) Bumps [mockito-inline](https://github.com/mockito/mockito) from 4.8.0 to 5.1.0. - [Release notes](https://github.com/mockito/mockito/releases) - [Commits](https://github.com/mockito/mockito/compare/v4.8.0...v5.1.0) --- updated-dependencies: - dependency-name: org.mockito:mockito-inline dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../webview_flutter_android/android/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/webview_flutter/webview_flutter_android/android/build.gradle b/packages/webview_flutter/webview_flutter_android/android/build.gradle index 7384f8d453da..f053954e5755 100644 --- a/packages/webview_flutter/webview_flutter_android/android/build.gradle +++ b/packages/webview_flutter/webview_flutter_android/android/build.gradle @@ -39,7 +39,7 @@ android { implementation 'androidx.annotation:annotation:1.5.0' implementation 'androidx.webkit:webkit:1.5.0' testImplementation 'junit:junit:4.13.2' - testImplementation 'org.mockito:mockito-inline:4.8.0' + testImplementation 'org.mockito:mockito-inline:5.1.0' testImplementation 'androidx.test:core:1.3.0' } From 1896f10ca07f9b989ae235444fd8c92bd758e01b Mon Sep 17 00:00:00 2001 From: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> Date: Tue, 31 Jan 2023 00:37:59 -0500 Subject: [PATCH 052/130] [webview_flutter_wkwebview][webview_flutter_android] Fixes bug where the `WebView`s could not be released (#6996) * fix and test non disposing webview * combine boolean * version bump * more pumpAndSettles * add more pumpAndSettles * update int tests * android test * fix version bump and unchange compile files * make _currentNavigationDelegate nullable * quick fix * accidental letter * small tests fixes * missing hashtag --- .../webview_flutter_android/CHANGELOG.md | 5 + .../webview_flutter_test.dart | 235 +++++++++------ .../lib/src/android_webview_controller.dart | 31 +- .../webview_flutter_android/pubspec.yaml | 2 +- .../test/android_webview_controller_test.dart | 13 + .../webview_flutter_wkwebview/CHANGELOG.md | 4 + .../webview_flutter_test.dart | 276 ++++++++++++++++-- .../lib/src/webkit_proxy.dart | 8 +- .../lib/src/webkit_webview_controller.dart | 34 ++- .../webview_flutter_wkwebview/pubspec.yaml | 2 +- .../test/webkit_webview_controller_test.dart | 23 +- .../test/webkit_webview_widget_test.dart | 13 +- 12 files changed, 492 insertions(+), 154 deletions(-) diff --git a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md index e1786d6cd7d0..1a490b5a579d 100644 --- a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md @@ -1,3 +1,8 @@ +## 3.2.3 + +* Fixes bug that prevented the web view from being garbage collected. +* Fixes bug causing a `LateInitializationError` when a `PlatformNavigationDelegate` is not provided. + ## 3.2.2 * Updates example code for `use_build_context_synchronously` lint. diff --git a/packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test.dart b/packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test.dart index 3f62053d0ac3..af144e55efba 100644 --- a/packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test.dart +++ b/packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test.dart @@ -17,6 +17,8 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; +import 'package:webview_flutter_android/src/android_webview.dart' as android; +import 'package:webview_flutter_android/src/android_webview_api_impls.dart'; import 'package:webview_flutter_android/src/instance_manager.dart'; import 'package:webview_flutter_android/src/weak_reference_utils.dart'; import 'package:webview_flutter_android/webview_flutter_android.dart'; @@ -58,15 +60,13 @@ Future main() async { ) ..loadRequest(LoadRequestParams(uri: Uri.parse(primaryUrl))); - await tester.pumpWidget( - Builder( - builder: (BuildContext context) { - return PlatformWebViewWidget( - PlatformWebViewWidgetCreationParams(controller: controller), - ).build(context); - }, - ), - ); + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); await pageFinished.future; @@ -96,6 +96,79 @@ Future main() async { expect(gcIdentifier, 0); }, timeout: const Timeout(Duration(seconds: 10))); + testWidgets( + 'WebView is released by garbage collection', + (WidgetTester tester) async { + final Completer webViewGCCompleter = Completer(); + + late final InstanceManager instanceManager; + instanceManager = + InstanceManager(onWeakReferenceRemoved: (int identifier) { + final Copyable instance = + instanceManager.getInstanceWithWeakReference(identifier)!; + if (instance is android.WebView && !webViewGCCompleter.isCompleted) { + webViewGCCompleter.complete(); + } + }); + + android.WebView.api = WebViewHostApiImpl( + instanceManager: instanceManager, + ); + android.WebSettings.api = WebSettingsHostApiImpl( + instanceManager: instanceManager, + ); + android.WebChromeClient.api = WebChromeClientHostApiImpl( + instanceManager: instanceManager, + ); + + await tester.pumpWidget( + Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + AndroidWebViewWidgetCreationParams( + instanceManager: instanceManager, + controller: PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ), + ), + ).build(context); + }, + ), + ); + await tester.pumpAndSettle(); + + await tester.pumpWidget( + Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + AndroidWebViewWidgetCreationParams( + instanceManager: instanceManager, + controller: PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ), + ), + ).build(context); + }, + ), + ); + await tester.pumpAndSettle(); + + // Force garbage collection. + await IntegrationTestWidgetsFlutterBinding.instance + .watchPerformance(() async { + await tester.pumpAndSettle(); + }); + + await tester.pumpAndSettle(); + await expectLater(webViewGCCompleter.future, completes); + + android.WebView.api = WebViewHostApiImpl(); + android.WebSettings.api = WebSettingsHostApiImpl(); + android.WebChromeClient.api = WebChromeClientHostApiImpl(); + }, + timeout: const Timeout(Duration(seconds: 10)), + ); + testWidgets('runJavaScriptReturningResult', (WidgetTester tester) async { final Completer pageFinished = Completer(); @@ -110,15 +183,13 @@ Future main() async { ) ..loadRequest(LoadRequestParams(uri: Uri.parse(primaryUrl))); - await tester.pumpWidget( - Builder( - builder: (BuildContext context) { - return PlatformWebViewWidget( - PlatformWebViewWidgetCreationParams(controller: controller), - ).build(context); - }, - ), - ); + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); await pageFinished.future; @@ -151,15 +222,13 @@ Future main() async { ), ); - await tester.pumpWidget( - Builder( - builder: (BuildContext context) { - return PlatformWebViewWidget( - PlatformWebViewWidgetCreationParams(controller: controller), - ).build(context); - }, - ), - ); + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); await pageLoads.stream.firstWhere((String url) => url == headersUrl); @@ -195,15 +264,13 @@ Future main() async { 'data:text/html;charset=utf-8;base64,PCFET0NUWVBFIGh0bWw+', ); - await tester.pumpWidget( - Builder( - builder: (BuildContext context) { - return PlatformWebViewWidget( - PlatformWebViewWidgetCreationParams(controller: controller), - ).build(context); - }, - ), - ); + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); await pageFinished.future; @@ -258,15 +325,13 @@ Future main() async { ..setUserAgent('Custom_User_Agent1') ..loadRequest(LoadRequestParams(uri: Uri.parse('about:blank'))); - await tester.pumpWidget( - Builder( - builder: (BuildContext context) { - return PlatformWebViewWidget( - PlatformWebViewWidgetCreationParams(controller: controller), - ).build(context); - }, - ), - ); + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); await pageFinished.future; @@ -335,15 +400,13 @@ Future main() async { ), ); - await tester.pumpWidget( - Builder( - builder: (BuildContext context) { - return PlatformWebViewWidget( - PlatformWebViewWidgetCreationParams(controller: controller), - ).build(context); - }, - ), - ); + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); await pageLoaded.future; @@ -369,15 +432,13 @@ Future main() async { ), ); - await tester.pumpWidget( - Builder( - builder: (BuildContext context) { - return PlatformWebViewWidget( - PlatformWebViewWidgetCreationParams(controller: controller), - ).build(context); - }, - ), - ); + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); await pageLoaded.future; @@ -491,15 +552,13 @@ Future main() async { ), ); - await tester.pumpWidget( - Builder( - builder: (BuildContext context) { - return PlatformWebViewWidget( - PlatformWebViewWidgetCreationParams(controller: controller), - ).build(context); - }, - ), - ); + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); await pageLoaded.future; @@ -525,15 +584,13 @@ Future main() async { ), ); - await tester.pumpWidget( - Builder( - builder: (BuildContext context) { - return PlatformWebViewWidget( - PlatformWebViewWidgetCreationParams(controller: controller), - ).build(context); - }, - ), - ); + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); await pageLoaded.future; @@ -573,15 +630,13 @@ Future main() async { ), ); - await tester.pumpWidget( - Builder( - builder: (BuildContext context) { - return PlatformWebViewWidget( - PlatformWebViewWidgetCreationParams(controller: controller), - ).build(context); - }, - ), - ); + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); await pageLoaded.future; diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart index 6f4af3ee7476..fd287a515c65 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart @@ -96,18 +96,20 @@ class AndroidWebViewController extends PlatformWebViewController { ); late final android_webview.WebChromeClient _webChromeClient = - withWeakReferenceTo(this, - (WeakReference weakReference) { - return _androidWebViewParams.androidWebViewProxy - .createAndroidWebChromeClient( - onProgressChanged: (android_webview.WebView webView, int progress) { - if (weakReference.target?._currentNavigationDelegate._onProgress != + _androidWebViewParams.androidWebViewProxy.createAndroidWebChromeClient( + onProgressChanged: withWeakReferenceTo(this, + (WeakReference weakReference) { + return (android_webview.WebView webView, int progress) { + if (weakReference.target?._currentNavigationDelegate?._onProgress != null) { weakReference - .target!._currentNavigationDelegate._onProgress!(progress); + .target!._currentNavigationDelegate!._onProgress!(progress); } - }, - onShowFileChooser: (android_webview.WebView webView, + }; + }), + onShowFileChooser: withWeakReferenceTo(this, + (WeakReference weakReference) { + return (android_webview.WebView webView, android_webview.FileChooserParams params) async { if (weakReference.target?._onShowFileSelectorCallback != null) { return weakReference.target!._onShowFileSelectorCallback!( @@ -115,9 +117,9 @@ class AndroidWebViewController extends PlatformWebViewController { ); } return []; - }, - ); - }); + }; + }), + ); /// The native [android_webview.FlutterAssetManager] allows managing assets. late final android_webview.FlutterAssetManager _flutterAssetManager = @@ -126,10 +128,7 @@ class AndroidWebViewController extends PlatformWebViewController { final Map _javaScriptChannelParams = {}; - // The keeps a reference to the current NavigationDelegate so that the - // callback methods remain reachable. - // ignore: unused_field - late AndroidNavigationDelegate _currentNavigationDelegate; + AndroidNavigationDelegate? _currentNavigationDelegate; Future> Function(FileSelectorParams)? _onShowFileSelectorCallback; diff --git a/packages/webview_flutter/webview_flutter_android/pubspec.yaml b/packages/webview_flutter/webview_flutter_android/pubspec.yaml index 81255dfa0f93..30ea5d7823e9 100644 --- a/packages/webview_flutter/webview_flutter_android/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_android/pubspec.yaml @@ -2,7 +2,7 @@ name: webview_flutter_android description: A Flutter plugin that provides a WebView widget on Android. repository: https://github.com/flutter/plugins/tree/main/packages/webview_flutter/webview_flutter_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22 -version: 3.2.2 +version: 3.2.3 environment: sdk: ">=2.17.0 <3.0.0" diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.dart index 80fa1924487c..03e71ec5d987 100644 --- a/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.dart +++ b/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.dart @@ -539,6 +539,19 @@ void main() { expect(callbackProgress, 42); }); + test('onProgress does not cause LateInitializationError', () { + // ignore: unused_local_variable + final AndroidWebViewController controller = createControllerWithMocks( + createWebChromeClient: CapturingWebChromeClient.new, + ); + + // Should not cause LateInitializationError + CapturingWebChromeClient.lastCreatedDelegate.onProgressChanged!( + android_webview.WebView.detached(), + 42, + ); + }); + test('setOnShowFileSelector', () async { late final Future> Function( android_webview.WebView webView, diff --git a/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md b/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md index a9cb87f57c65..f79058c80216 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md @@ -1,3 +1,7 @@ +## 3.0.4 + +* Fixes bug that prevented the web view from being garbage collected. + ## 3.0.3 * Updates example code for `use_build_context_synchronously` lint. diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/integration_test/webview_flutter_test.dart b/packages/webview_flutter/webview_flutter_wkwebview/example/integration_test/webview_flutter_test.dart index 946f27b5df83..16411b8140a5 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/integration_test/webview_flutter_test.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/integration_test/webview_flutter_test.dart @@ -21,6 +21,7 @@ import 'package:integration_test/integration_test.dart'; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; import 'package:webview_flutter_wkwebview/src/common/instance_manager.dart'; import 'package:webview_flutter_wkwebview/src/common/weak_reference_utils.dart'; +import 'package:webview_flutter_wkwebview/src/web_kit/web_kit.dart'; import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart'; Future main() async { @@ -47,7 +48,7 @@ Future main() async { final String headersUrl = '$prefixUrl/headers'; testWidgets( - 'withWeakRefenceTo allows encapsulating class to be garbage collected', + 'withWeakReferenceTo allows encapsulating class to be garbage collected', (WidgetTester tester) async { final Completer gcCompleter = Completer(); final InstanceManager instanceManager = InstanceManager( @@ -68,21 +69,102 @@ Future main() async { expect(gcIdentifier, 0); }, timeout: const Timeout(Duration(seconds: 10))); + testWidgets( + 'WKWebView is released by garbage collection', + (WidgetTester tester) async { + final Completer webViewGCCompleter = Completer(); + + late final InstanceManager instanceManager; + instanceManager = + InstanceManager(onWeakReferenceRemoved: (int identifier) { + final Copyable instance = + instanceManager.getInstanceWithWeakReference(identifier)!; + if (instance is WKWebView && !webViewGCCompleter.isCompleted) { + webViewGCCompleter.complete(); + } + }); + + await tester.pumpWidget( + Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + WebKitWebViewWidgetCreationParams( + instanceManager: instanceManager, + controller: PlatformWebViewController( + WebKitWebViewControllerCreationParams( + instanceManager: instanceManager, + ), + ), + ), + ).build(context); + }, + ), + ); + await tester.pumpAndSettle(); + + await tester.pumpWidget(Container()); + + // Force garbage collection. + await IntegrationTestWidgetsFlutterBinding.instance + .watchPerformance(() async { + await tester.pumpAndSettle(); + }); + + await expectLater(webViewGCCompleter.future, completes); + }, + timeout: const Timeout(Duration(seconds: 10)), + ); + testWidgets('loadRequest', (WidgetTester tester) async { + final Completer pageFinished = Completer(); + final PlatformWebViewController controller = PlatformWebViewController( const PlatformWebViewControllerCreationParams(), - ); - controller.loadRequest(LoadRequestParams(uri: Uri.parse(primaryUrl))); + ) + ..setPlatformNavigationDelegate( + PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + )..setOnPageFinished((_) => pageFinished.complete()), + ) + ..loadRequest(LoadRequestParams(uri: Uri.parse(primaryUrl))); + + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + + await pageFinished.future; final String? currentUrl = await controller.currentUrl(); expect(currentUrl, primaryUrl); }); testWidgets('runJavaScriptReturningResult', (WidgetTester tester) async { + final Completer pageFinished = Completer(); + final PlatformWebViewController controller = PlatformWebViewController( const PlatformWebViewControllerCreationParams(), - ); - controller.loadRequest(LoadRequestParams(uri: Uri.parse(primaryUrl))); + ) + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setPlatformNavigationDelegate( + PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + )..setOnPageFinished((_) => pageFinished.complete()), + ) + ..loadRequest(LoadRequestParams(uri: Uri.parse(primaryUrl))); + + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + + await pageFinished.future; await expectLater( controller.runJavaScriptReturningResult('1 + 1'), @@ -113,6 +195,14 @@ Future main() async { ), ); + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + await pageLoads.stream.firstWhere((String url) => url == headersUrl); final String content = await controller.runJavaScriptReturningResult( @@ -147,6 +237,14 @@ Future main() async { 'data:text/html;charset=utf-8;base64,PCFET0NUWVBFIGh0bWw+', ); + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + await pageFinished.future; await controller.runJavaScript('Echo.postMessage("hello");'); @@ -184,6 +282,14 @@ Future main() async { ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setUserAgent('Custom_User_Agent1'); + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + final String customUserAgent2 = await _getUserAgent(controller); expect(customUserAgent2, 'Custom_User_Agent1'); }); @@ -250,6 +356,14 @@ Future main() async { ), ); + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + await pageLoaded.future; bool isPaused = @@ -274,6 +388,14 @@ Future main() async { ), ); + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + await pageLoaded.future; isPaused = @@ -447,6 +569,14 @@ Future main() async { ), ); + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + await pageLoaded.future; bool isPaused = @@ -471,6 +601,14 @@ Future main() async { ), ); + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + await pageLoaded.future; isPaused = @@ -509,6 +647,14 @@ Future main() async { ), ); + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + await pageLoaded.future; // On at least iOS, it does not appear to be guaranteed that the native @@ -565,6 +711,14 @@ Future main() async { ), ); + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + await pageLoaded.future; await tester.pumpAndSettle(const Duration(seconds: 3)); @@ -599,8 +753,7 @@ Future main() async { '${base64Encode(const Utf8Encoder().convert(blankPage))}'; testWidgets('can allow requests', (WidgetTester tester) async { - final StreamController pageLoads = - StreamController.broadcast(); + Completer pageLoaded = Completer(); final PlatformWebViewController controller = PlatformWebViewController( WebKitWebViewControllerCreationParams(), @@ -610,7 +763,7 @@ Future main() async { WebKitNavigationDelegate( const WebKitNavigationDelegateCreationParams(), ) - ..setOnPageFinished((String url) => pageLoads.add(url)) + ..setOnPageFinished((_) => pageLoaded.complete()) ..setOnNavigationRequest((NavigationRequest navigationRequest) { return (navigationRequest.url.contains('youtube.com')) ? NavigationDecision.prevent @@ -621,10 +774,20 @@ Future main() async { LoadRequestParams(uri: Uri.parse(blankPageEncoded)), ); - await pageLoads.stream.first; // Wait for initial page load. + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + + await pageLoaded.future; // Wait for initial page load. + + pageLoaded = Completer(); await controller.runJavaScript('location.href = "$secondaryUrl"'); + await pageLoaded.future; - await pageLoads.stream.first; // Wait for the next page load. final String? currentUrl = await controller.currentUrl(); expect(currentUrl, secondaryUrl); }); @@ -633,7 +796,7 @@ Future main() async { final Completer errorCompleter = Completer(); - PlatformWebViewController( + final PlatformWebViewController controller = PlatformWebViewController( WebKitWebViewControllerCreationParams(), ) ..setJavaScriptMode(JavaScriptMode.unrestricted) @@ -648,6 +811,14 @@ Future main() async { LoadRequestParams(uri: Uri.parse('https://www.notawebsite..com')), ); + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + final WebResourceError error = await errorCompleter.future; expect(error, isNotNull); @@ -660,7 +831,7 @@ Future main() async { Completer(); final Completer pageFinishCompleter = Completer(); - PlatformWebViewController( + final PlatformWebViewController controller = PlatformWebViewController( WebKitWebViewControllerCreationParams(), ) ..setJavaScriptMode(JavaScriptMode.unrestricted) @@ -681,6 +852,14 @@ Future main() async { ), ); + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + expect(errorCompleter.future, doesNotComplete); await pageFinishCompleter.future; }); @@ -706,7 +885,7 @@ Future main() async { Completer(); final Completer pageFinishCompleter = Completer(); - PlatformWebViewController( + final PlatformWebViewController controller = PlatformWebViewController( WebKitWebViewControllerCreationParams(), ) ..setJavaScriptMode(JavaScriptMode.unrestricted) @@ -727,14 +906,21 @@ Future main() async { ), ); + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + expect(errorCompleter.future, doesNotComplete); await pageFinishCompleter.future; }, ); testWidgets('can block requests', (WidgetTester tester) async { - final StreamController pageLoads = - StreamController.broadcast(); + Completer pageLoaded = Completer(); final PlatformWebViewController controller = PlatformWebViewController( WebKitWebViewControllerCreationParams(), @@ -744,7 +930,7 @@ Future main() async { WebKitNavigationDelegate( const WebKitNavigationDelegateCreationParams(), ) - ..setOnPageFinished((String url) => pageLoads.add(url)) + ..setOnPageFinished((_) => pageLoaded.complete()) ..setOnNavigationRequest((NavigationRequest navigationRequest) { return (navigationRequest.url.contains('youtube.com')) ? NavigationDecision.prevent @@ -753,22 +939,31 @@ Future main() async { ) ..loadRequest(LoadRequestParams(uri: Uri.parse(blankPageEncoded))); - await pageLoads.stream.first; // Wait for initial page load. + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + + await pageLoaded.future; // Wait for initial page load. + + pageLoaded = Completer(); await controller .runJavaScript('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 + await pageLoaded.future .timeout(const Duration(milliseconds: 500), onTimeout: () => ''); final String? currentUrl = await controller.currentUrl(); expect(currentUrl, isNot(contains('youtube.com'))); }); testWidgets('supports asynchronous decisions', (WidgetTester tester) async { - final StreamController pageLoads = - StreamController.broadcast(); + Completer pageLoaded = Completer(); final PlatformWebViewController controller = PlatformWebViewController( WebKitWebViewControllerCreationParams(), @@ -778,7 +973,7 @@ Future main() async { WebKitNavigationDelegate( const WebKitNavigationDelegateCreationParams(), ) - ..setOnPageFinished((String url) => pageLoads.add(url)) + ..setOnPageFinished((_) => pageLoaded.complete()) ..setOnNavigationRequest( (NavigationRequest navigationRequest) async { NavigationDecision decision = NavigationDecision.prevent; @@ -790,10 +985,20 @@ Future main() async { ) ..loadRequest(LoadRequestParams(uri: Uri.parse(blankPageEncoded))); - await pageLoads.stream.first; // Wait for initial page load. + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + + await pageLoaded.future; // Wait for initial page load. + + pageLoaded = Completer(); await controller.runJavaScript('location.href = "$secondaryUrl"'); - await pageLoads.stream.first; // Wait for second page to load. + await pageLoaded.future; // Wait for second page to load. final String? currentUrl = await controller.currentUrl(); expect(currentUrl, secondaryUrl); }); @@ -807,6 +1012,14 @@ Future main() async { ..setAllowsBackForwardNavigationGestures(true) ..loadRequest(LoadRequestParams(uri: Uri.parse(primaryUrl))); + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + final String? currentUrl = await controller.currentUrl(); expect(currentUrl, primaryUrl); }); @@ -824,6 +1037,15 @@ Future main() async { )..setOnPageFinished((_) => pageLoaded.complete())); await controller.runJavaScript('window.open("$primaryUrl", "_blank")'); + + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + await pageLoaded.future; final String? currentUrl = await controller.currentUrl(); expect(currentUrl, primaryUrl); @@ -843,6 +1065,14 @@ Future main() async { )..setOnPageFinished((_) => pageLoaded.complete())) ..loadRequest(LoadRequestParams(uri: Uri.parse(primaryUrl))); + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + expect(controller.currentUrl(), completion(primaryUrl)); await pageLoaded.future; pageLoaded = Completer(); diff --git a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webkit_proxy.dart b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webkit_proxy.dart index 2cdc7e269454..3e8d6796069b 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webkit_proxy.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webkit_proxy.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'common/instance_manager.dart'; import 'foundation/foundation.dart'; import 'web_kit/web_kit.dart'; @@ -39,10 +40,13 @@ class WebKitProxy { Map change, )? observeValue, + InstanceManager? instanceManager, }) createWebView; /// Constructs a [WKWebViewConfiguration]. - final WKWebViewConfiguration Function() createWebViewConfiguration; + final WKWebViewConfiguration Function({ + InstanceManager? instanceManager, + }) createWebViewConfiguration; /// Constructs a [WKScriptMessageHandler]. final WKScriptMessageHandler Function({ @@ -72,7 +76,7 @@ class WebKitProxy { void Function(WKWebView webView)? webViewWebContentProcessDidTerminate, }) createNavigationDelegate; - /// Contructs a [WKUIDelegate]. + /// Constructs a [WKUIDelegate]. final WKUIDelegate Function({ void Function( WKWebView webView, diff --git a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webkit_webview_controller.dart b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webkit_webview_controller.dart index dc90906d78f4..02b5b73b5971 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webkit_webview_controller.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webkit_webview_controller.dart @@ -49,7 +49,12 @@ class WebKitWebViewControllerCreationParams PlaybackMediaTypes.video, }, this.allowsInlineMediaPlayback = false, - }) : _configuration = webKitProxy.createWebViewConfiguration() { + @visibleForTesting InstanceManager? instanceManager, + }) : _instanceManager = instanceManager ?? NSObject.globalInstanceManager { + _configuration = webKitProxy.createWebViewConfiguration( + instanceManager: _instanceManager, + ); + if (mediaTypesRequiringUserAction.isEmpty) { _configuration.setMediaTypesRequiringUserActionForPlayback( {WKAudiovisualMediaType.none}, @@ -79,13 +84,15 @@ class WebKitWebViewControllerCreationParams PlaybackMediaTypes.video, }, bool allowsInlineMediaPlayback = false, + @visibleForTesting InstanceManager? instanceManager, }) : this( webKitProxy: webKitProxy, mediaTypesRequiringUserAction: mediaTypesRequiringUserAction, allowsInlineMediaPlayback: allowsInlineMediaPlayback, + instanceManager: instanceManager, ); - final WKWebViewConfiguration _configuration; + late final WKWebViewConfiguration _configuration; /// Media types that require a user gesture to begin playing. /// @@ -102,6 +109,10 @@ class WebKitWebViewControllerCreationParams /// native library. @visibleForTesting final WebKitProxy webKitProxy; + + // Maintains instances used to communicate with the native objects they + // represent. + final InstanceManager _instanceManager; } /// An implementation of [PlatformWebViewController] with the WebKit api. @@ -122,12 +133,12 @@ class WebKitWebViewController extends PlatformWebViewController { } /// The WebKit WebView being controlled. - late final WKWebView _webView = withWeakRefenceTo(this, ( - WeakReference weakReference, - ) { - return _webKitParams.webKitProxy.createWebView( - _webKitParams._configuration, - observeValue: ( + late final WKWebView _webView = _webKitParams.webKitProxy.createWebView( + _webKitParams._configuration, + observeValue: withWeakRefenceTo(this, ( + WeakReference weakReference, + ) { + return ( String keyPath, NSObject object, Map change, @@ -139,9 +150,10 @@ class WebKitWebViewController extends PlatformWebViewController { change[NSKeyValueChangeKey.newValue]! as double; progressCallback((progress * 100).round()); } - }, - ); - }); + }; + }), + instanceManager: _webKitParams._instanceManager, + ); final Map _javaScriptChannelParams = {}; diff --git a/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml b/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml index c41bce18cae6..15e8ac5678f8 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml @@ -2,7 +2,7 @@ name: webview_flutter_wkwebview description: A Flutter plugin that provides a WebView widget based on Apple's WKWebView control. repository: https://github.com/flutter/plugins/tree/main/packages/webview_flutter/webview_flutter_wkwebview issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22 -version: 3.0.3 +version: 3.0.4 environment: sdk: ">=2.17.0 <3.0.0" diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_controller_test.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_controller_test.dart index fc06db24f055..0360c13b052a 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_controller_test.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_controller_test.dart @@ -13,6 +13,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; +import 'package:webview_flutter_wkwebview/src/common/instance_manager.dart'; import 'package:webview_flutter_wkwebview/src/foundation/foundation.dart'; import 'package:webview_flutter_wkwebview/src/ui_kit/ui_kit.dart'; import 'package:webview_flutter_wkwebview/src/web_kit/web_kit.dart'; @@ -49,6 +50,7 @@ void main() { })? createMockWebView, MockWKWebViewConfiguration? mockWebViewConfiguration, + InstanceManager? instanceManager, }) { final MockWKWebViewConfiguration nonNullMockWebViewConfiguration = mockWebViewConfiguration ?? MockWKWebViewConfiguration(); @@ -57,7 +59,9 @@ void main() { final PlatformWebViewControllerCreationParams controllerCreationParams = WebKitWebViewControllerCreationParams( webKitProxy: WebKitProxy( - createWebViewConfiguration: () => nonNullMockWebViewConfiguration, + createWebViewConfiguration: ({InstanceManager? instanceManager}) { + return nonNullMockWebViewConfiguration; + }, createWebView: ( _, { void Function( @@ -66,6 +70,7 @@ void main() { Map change, )? observeValue, + InstanceManager? instanceManager, }) { nonNullMockWebView = createMockWebView == null ? MockWKWebView() @@ -104,7 +109,9 @@ void main() { WebKitWebViewControllerCreationParams( webKitProxy: WebKitProxy( - createWebViewConfiguration: () => mockConfiguration, + createWebViewConfiguration: ({InstanceManager? instanceManager}) { + return mockConfiguration; + }, ), allowsInlineMediaPlayback: true, ); @@ -120,7 +127,9 @@ void main() { WebKitWebViewControllerCreationParams( webKitProxy: WebKitProxy( - createWebViewConfiguration: () => mockConfiguration, + createWebViewConfiguration: ({InstanceManager? instanceManager}) { + return mockConfiguration; + }, ), mediaTypesRequiringUserAction: const { PlaybackMediaTypes.video, @@ -143,7 +152,9 @@ void main() { WebKitWebViewControllerCreationParams( webKitProxy: WebKitProxy( - createWebViewConfiguration: () => mockConfiguration, + createWebViewConfiguration: ({InstanceManager? instanceManager}) { + return mockConfiguration; + }, ), ); @@ -164,7 +175,9 @@ void main() { WebKitWebViewControllerCreationParams( webKitProxy: WebKitProxy( - createWebViewConfiguration: () => mockConfiguration, + createWebViewConfiguration: ({InstanceManager? instanceManager}) { + return mockConfiguration; + }, ), mediaTypesRequiringUserAction: const {}, ); diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_widget_test.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_widget_test.dart index 2e0d6e3e9af3..2a6434be4f03 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_widget_test.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_widget_test.dart @@ -19,7 +19,7 @@ void main() { group('WebKitWebViewWidget', () { testWidgets('build', (WidgetTester tester) async { - final InstanceManager instanceManager = InstanceManager( + final InstanceManager testInstanceManager = InstanceManager( onWeakReferenceRemoved: (_) {}, ); @@ -34,14 +34,17 @@ void main() { Map change, )? observeValue, + InstanceManager? instanceManager, }) { final WKWebView webView = WKWebView.detached( - instanceManager: instanceManager, + instanceManager: testInstanceManager, ); - instanceManager.addDartCreatedInstance(webView); + testInstanceManager.addDartCreatedInstance(webView); return webView; }, - createWebViewConfiguration: () => MockWKWebViewConfiguration(), + createWebViewConfiguration: ({InstanceManager? instanceManager}) { + return MockWKWebViewConfiguration(); + }, ), ), ); @@ -50,7 +53,7 @@ void main() { WebKitWebViewWidgetCreationParams( key: const Key('keyValue'), controller: controller, - instanceManager: instanceManager, + instanceManager: testInstanceManager, ), ); From a494825fae2db112816b6f2d49b6d12f92609444 Mon Sep 17 00:00:00 2001 From: Camille Simon <43054281+camsim99@users.noreply.github.com> Date: Mon, 30 Jan 2023 21:38:02 -0800 Subject: [PATCH 053/130] [camerax] Allow instance manager to create identical objects (#7034) * Make changes amd add test * Update changelog * Add comment * Fix spelling * Update packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/InstanceManager.java Co-authored-by: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> --------- Co-authored-by: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> --- .../camera/camera_android_camerax/CHANGELOG.md | 1 + .../camerax/CameraSelectorHostApiImpl.java | 4 +++- .../flutter/plugins/camerax/InstanceManager.java | 7 +++---- .../camerax/ProcessCameraProviderHostApiImpl.java | 12 +++++++++--- .../plugins/camerax/InstanceManagerTest.java | 15 +++++++++++++++ 5 files changed, 31 insertions(+), 8 deletions(-) diff --git a/packages/camera/camera_android_camerax/CHANGELOG.md b/packages/camera/camera_android_camerax/CHANGELOG.md index e248623ef678..d5355c60c751 100644 --- a/packages/camera/camera_android_camerax/CHANGELOG.md +++ b/packages/camera/camera_android_camerax/CHANGELOG.md @@ -7,3 +7,4 @@ * Bump CameraX version to 1.3.0-alpha02. * Adds Camera and UseCase classes, along with methods for binding UseCases to a lifecycle with the ProcessCameraProvider. * Bump CameraX version to 1.3.0-alpha03 and Kotlin version to 1.8.0. +* Changes instance manager to allow the separate creation of identical objects. diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraSelectorHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraSelectorHostApiImpl.java index 87c69dea9e1c..0528d584d26e 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraSelectorHostApiImpl.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraSelectorHostApiImpl.java @@ -60,7 +60,9 @@ public List filter(@NonNull Long identifier, @NonNull List cameraInf List filteredCameraInfosIds = new ArrayList(); for (CameraInfo cameraInfo : filteredCameraInfos) { - cameraInfoFlutterApiImpl.create(cameraInfo, result -> {}); + if (!instanceManager.containsInstance(cameraInfo)) { + cameraInfoFlutterApiImpl.create(cameraInfo, result -> {}); + } Long filteredCameraInfoId = instanceManager.getIdentifierForStrongReference(cameraInfo); filteredCameraInfosIds.add(filteredCameraInfoId); } diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/InstanceManager.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/InstanceManager.java index 9b549d7bd1ea..8212d1267a19 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/InstanceManager.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/InstanceManager.java @@ -122,16 +122,15 @@ public void addDartCreatedInstance(Object instance, long identifier) { /** * Adds a new instance that was instantiated from the host platform. * - *

If an instance has already been added, the identifier of the instance will be returned. + *

If an instance has already been added, this will replace it. {@code #containsInstance} can + * be used to check if the object has already been added to avoid this. * * @param instance the instance to be stored. * @return the unique identifier stored with instance. */ public long addHostCreatedInstance(Object instance) { assertManagerIsNotClosed(); - if (containsInstance(instance)) { - return getIdentifierForStrongReference(instance); - } + final long identifier = nextIdentifier++; addInstance(instance, identifier); return identifier; diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ProcessCameraProviderHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ProcessCameraProviderHostApiImpl.java index f82f18f054c9..e7036e7090c1 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ProcessCameraProviderHostApiImpl.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ProcessCameraProviderHostApiImpl.java @@ -66,7 +66,9 @@ public void getInstance(GeneratedCameraXLibrary.Result result) { final ProcessCameraProviderFlutterApiImpl flutterApi = new ProcessCameraProviderFlutterApiImpl(binaryMessenger, instanceManager); - flutterApi.create(processCameraProvider, reply -> {}); + if (!instanceManager.containsInstance(processCameraProvider)) { + flutterApi.create(processCameraProvider, reply -> {}); + } result.success(instanceManager.getIdentifierForStrongReference(processCameraProvider)); } catch (Exception e) { result.error(e); @@ -87,7 +89,9 @@ public List getAvailableCameraInfos(@NonNull Long identifier) { new CameraInfoFlutterApiImpl(binaryMessenger, instanceManager); for (CameraInfo cameraInfo : availableCameras) { - cameraInfoFlutterApi.create(cameraInfo, result -> {}); + if (!instanceManager.containsInstance(cameraInfo)) { + cameraInfoFlutterApi.create(cameraInfo, result -> {}); + } availableCamerasIds.add(instanceManager.getIdentifierForStrongReference(cameraInfo)); } return availableCamerasIds; @@ -122,7 +126,9 @@ public Long bindToLifecycle( final CameraFlutterApiImpl cameraFlutterApi = new CameraFlutterApiImpl(binaryMessenger, instanceManager); - cameraFlutterApi.create(camera, result -> {}); + if (!instanceManager.containsInstance(camera)) { + cameraFlutterApi.create(camera, result -> {}); + } return instanceManager.getIdentifierForStrongReference(camera); } diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/InstanceManagerTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/InstanceManagerTest.java index 3878e05a40e8..e2e012dc35fb 100644 --- a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/InstanceManagerTest.java +++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/InstanceManagerTest.java @@ -5,6 +5,7 @@ package io.flutter.plugins.camerax; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -40,6 +41,20 @@ public void addHostCreatedInstance() { instanceManager.close(); } + @Test + public void addHostCreatedInstance_createsSameInstanceTwice() { + final InstanceManager instanceManager = InstanceManager.open(identifier -> {}); + + final Object object = new Object(); + long firstIdentifier = instanceManager.addHostCreatedInstance(object); + long secondIdentifier = instanceManager.addHostCreatedInstance(object); + + assertNotEquals(firstIdentifier, secondIdentifier); + assertTrue(instanceManager.containsInstance(object)); + + instanceManager.close(); + } + @Test public void remove() { final InstanceManager instanceManager = InstanceManager.open(identifier -> {}); From 6ef73da26e521acc7fec823fb3b554c8d35e8c03 Mon Sep 17 00:00:00 2001 From: stuartmorgan Date: Tue, 31 Jan 2023 03:47:05 -0800 Subject: [PATCH 054/130] [ci] Increase heavy workload memory (#7065) Android builds are increasingly running into OOM errors; this increases the machine configuration from 12GB to 16GB to hopefully avoid the issue. --- .cirrus.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.cirrus.yml b/.cirrus.yml index 080dc914b0eb..66a471020189 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -228,7 +228,7 @@ task: zone: us-central1-a namespace: default cpu: 4 - memory: 12G + memory: 16G matrix: ### Android tasks ### - name: android-platform_tests From 9da327ca39c4ab1ba51ec3bb9eb756759a305a2e Mon Sep 17 00:00:00 2001 From: stuartmorgan Date: Tue, 31 Jan 2023 06:21:20 -0800 Subject: [PATCH 055/130] [various] Update to use sharedDarwinSource (#7027) * Update shared_preferences * Update IAP --- .../in_app_purchase/in_app_purchase_storekit/CHANGELOG.md | 6 +++++- .../{shared => darwin}/Classes/FIAObjectTranslator.h | 0 .../{shared => darwin}/Classes/FIAObjectTranslator.m | 0 .../{shared => darwin}/Classes/FIAPPaymentQueueDelegate.h | 0 .../{shared => darwin}/Classes/FIAPPaymentQueueDelegate.m | 0 .../{shared => darwin}/Classes/FIAPReceiptManager.h | 0 .../{shared => darwin}/Classes/FIAPReceiptManager.m | 0 .../{shared => darwin}/Classes/FIAPRequestHandler.h | 0 .../{shared => darwin}/Classes/FIAPRequestHandler.m | 0 .../{shared => darwin}/Classes/FIAPaymentQueueHandler.h | 0 .../{shared => darwin}/Classes/FIAPaymentQueueHandler.m | 0 .../{shared => darwin}/Classes/FIATransactionCache.h | 0 .../{shared => darwin}/Classes/FIATransactionCache.m | 0 .../{shared => darwin}/Classes/InAppPurchasePlugin.h | 0 .../{shared => darwin}/Classes/InAppPurchasePlugin.m | 0 .../{shared => darwin}/in_app_purchase_storekit.podspec | 0 .../in_app_purchase_storekit/ios/Assets/.gitkeep | 1 - .../ios/Classes/FIAObjectTranslator.h | 2 +- .../ios/Classes/FIAObjectTranslator.m | 2 +- .../ios/Classes/FIAPPaymentQueueDelegate.h | 2 +- .../ios/Classes/FIAPPaymentQueueDelegate.m | 2 +- .../ios/Classes/FIAPReceiptManager.h | 2 +- .../ios/Classes/FIAPReceiptManager.m | 2 +- .../ios/Classes/FIAPRequestHandler.h | 2 +- .../ios/Classes/FIAPRequestHandler.m | 2 +- .../ios/Classes/FIAPaymentQueueHandler.h | 2 +- .../ios/Classes/FIAPaymentQueueHandler.m | 2 +- .../ios/Classes/FIATransactionCache.h | 2 +- .../ios/Classes/FIATransactionCache.m | 2 +- .../ios/Classes/InAppPurchasePlugin.h | 2 +- .../ios/Classes/InAppPurchasePlugin.m | 2 +- .../ios/in_app_purchase_storekit.podspec | 2 +- .../in_app_purchase_storekit/macos/Assets/.gitkeep | 1 - .../macos/Classes/FIAObjectTranslator.h | 2 +- .../macos/Classes/FIAObjectTranslator.m | 2 +- .../macos/Classes/FIAPPaymentQueueDelegate.h | 2 +- .../macos/Classes/FIAPPaymentQueueDelegate.m | 2 +- .../macos/Classes/FIAPReceiptManager.h | 2 +- .../macos/Classes/FIAPReceiptManager.m | 2 +- .../macos/Classes/FIAPRequestHandler.h | 2 +- .../macos/Classes/FIAPRequestHandler.m | 2 +- .../macos/Classes/FIAPaymentQueueHandler.h | 2 +- .../macos/Classes/FIAPaymentQueueHandler.m | 2 +- .../macos/Classes/FIATransactionCache.h | 2 +- .../macos/Classes/FIATransactionCache.m | 2 +- .../macos/Classes/InAppPurchasePlugin.h | 2 +- .../macos/Classes/InAppPurchasePlugin.m | 2 +- .../macos/in_app_purchase_storekit.podspec | 2 +- .../in_app_purchase/in_app_purchase_storekit/pubspec.yaml | 4 +++- .../in_app_purchase_storekit/shared/Assets/.gitkeep | 0 .../shared_preferences_foundation/CHANGELOG.md | 3 ++- .../shared_preferences_foundation/pubspec.yaml | 6 +++--- 52 files changed, 43 insertions(+), 38 deletions(-) rename packages/in_app_purchase/in_app_purchase_storekit/{shared => darwin}/Classes/FIAObjectTranslator.h (100%) rename packages/in_app_purchase/in_app_purchase_storekit/{shared => darwin}/Classes/FIAObjectTranslator.m (100%) rename packages/in_app_purchase/in_app_purchase_storekit/{shared => darwin}/Classes/FIAPPaymentQueueDelegate.h (100%) rename packages/in_app_purchase/in_app_purchase_storekit/{shared => darwin}/Classes/FIAPPaymentQueueDelegate.m (100%) rename packages/in_app_purchase/in_app_purchase_storekit/{shared => darwin}/Classes/FIAPReceiptManager.h (100%) rename packages/in_app_purchase/in_app_purchase_storekit/{shared => darwin}/Classes/FIAPReceiptManager.m (100%) rename packages/in_app_purchase/in_app_purchase_storekit/{shared => darwin}/Classes/FIAPRequestHandler.h (100%) rename packages/in_app_purchase/in_app_purchase_storekit/{shared => darwin}/Classes/FIAPRequestHandler.m (100%) rename packages/in_app_purchase/in_app_purchase_storekit/{shared => darwin}/Classes/FIAPaymentQueueHandler.h (100%) rename packages/in_app_purchase/in_app_purchase_storekit/{shared => darwin}/Classes/FIAPaymentQueueHandler.m (100%) rename packages/in_app_purchase/in_app_purchase_storekit/{shared => darwin}/Classes/FIATransactionCache.h (100%) rename packages/in_app_purchase/in_app_purchase_storekit/{shared => darwin}/Classes/FIATransactionCache.m (100%) rename packages/in_app_purchase/in_app_purchase_storekit/{shared => darwin}/Classes/InAppPurchasePlugin.h (100%) rename packages/in_app_purchase/in_app_purchase_storekit/{shared => darwin}/Classes/InAppPurchasePlugin.m (100%) rename packages/in_app_purchase/in_app_purchase_storekit/{shared => darwin}/in_app_purchase_storekit.podspec (100%) delete mode 120000 packages/in_app_purchase/in_app_purchase_storekit/ios/Assets/.gitkeep delete mode 120000 packages/in_app_purchase/in_app_purchase_storekit/macos/Assets/.gitkeep delete mode 100644 packages/in_app_purchase/in_app_purchase_storekit/shared/Assets/.gitkeep diff --git a/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md b/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md index aef5d16a2a25..5d93cc085843 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md +++ b/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.3.5+1 + +* Uses the new `sharedDarwinSource` flag when available. + ## 0.3.5 * Updates minimum Flutter version to 3.0. @@ -13,7 +17,7 @@ ## 0.3.3 -* Supports adding discount information to AppStorePurchaseParam. +* Supports adding discount information to AppStorePurchaseParam. * Fixes iOS Promotional Offers bug which prevents them from working. ## 0.3.2+2 diff --git a/packages/in_app_purchase/in_app_purchase_storekit/shared/Classes/FIAObjectTranslator.h b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAObjectTranslator.h similarity index 100% rename from packages/in_app_purchase/in_app_purchase_storekit/shared/Classes/FIAObjectTranslator.h rename to packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAObjectTranslator.h diff --git a/packages/in_app_purchase/in_app_purchase_storekit/shared/Classes/FIAObjectTranslator.m b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAObjectTranslator.m similarity index 100% rename from packages/in_app_purchase/in_app_purchase_storekit/shared/Classes/FIAObjectTranslator.m rename to packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAObjectTranslator.m diff --git a/packages/in_app_purchase/in_app_purchase_storekit/shared/Classes/FIAPPaymentQueueDelegate.h b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPPaymentQueueDelegate.h similarity index 100% rename from packages/in_app_purchase/in_app_purchase_storekit/shared/Classes/FIAPPaymentQueueDelegate.h rename to packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPPaymentQueueDelegate.h diff --git a/packages/in_app_purchase/in_app_purchase_storekit/shared/Classes/FIAPPaymentQueueDelegate.m b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPPaymentQueueDelegate.m similarity index 100% rename from packages/in_app_purchase/in_app_purchase_storekit/shared/Classes/FIAPPaymentQueueDelegate.m rename to packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPPaymentQueueDelegate.m diff --git a/packages/in_app_purchase/in_app_purchase_storekit/shared/Classes/FIAPReceiptManager.h b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPReceiptManager.h similarity index 100% rename from packages/in_app_purchase/in_app_purchase_storekit/shared/Classes/FIAPReceiptManager.h rename to packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPReceiptManager.h diff --git a/packages/in_app_purchase/in_app_purchase_storekit/shared/Classes/FIAPReceiptManager.m b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPReceiptManager.m similarity index 100% rename from packages/in_app_purchase/in_app_purchase_storekit/shared/Classes/FIAPReceiptManager.m rename to packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPReceiptManager.m diff --git a/packages/in_app_purchase/in_app_purchase_storekit/shared/Classes/FIAPRequestHandler.h b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPRequestHandler.h similarity index 100% rename from packages/in_app_purchase/in_app_purchase_storekit/shared/Classes/FIAPRequestHandler.h rename to packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPRequestHandler.h diff --git a/packages/in_app_purchase/in_app_purchase_storekit/shared/Classes/FIAPRequestHandler.m b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPRequestHandler.m similarity index 100% rename from packages/in_app_purchase/in_app_purchase_storekit/shared/Classes/FIAPRequestHandler.m rename to packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPRequestHandler.m diff --git a/packages/in_app_purchase/in_app_purchase_storekit/shared/Classes/FIAPaymentQueueHandler.h b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPaymentQueueHandler.h similarity index 100% rename from packages/in_app_purchase/in_app_purchase_storekit/shared/Classes/FIAPaymentQueueHandler.h rename to packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPaymentQueueHandler.h diff --git a/packages/in_app_purchase/in_app_purchase_storekit/shared/Classes/FIAPaymentQueueHandler.m b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPaymentQueueHandler.m similarity index 100% rename from packages/in_app_purchase/in_app_purchase_storekit/shared/Classes/FIAPaymentQueueHandler.m rename to packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPaymentQueueHandler.m diff --git a/packages/in_app_purchase/in_app_purchase_storekit/shared/Classes/FIATransactionCache.h b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIATransactionCache.h similarity index 100% rename from packages/in_app_purchase/in_app_purchase_storekit/shared/Classes/FIATransactionCache.h rename to packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIATransactionCache.h diff --git a/packages/in_app_purchase/in_app_purchase_storekit/shared/Classes/FIATransactionCache.m b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIATransactionCache.m similarity index 100% rename from packages/in_app_purchase/in_app_purchase_storekit/shared/Classes/FIATransactionCache.m rename to packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIATransactionCache.m diff --git a/packages/in_app_purchase/in_app_purchase_storekit/shared/Classes/InAppPurchasePlugin.h b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.h similarity index 100% rename from packages/in_app_purchase/in_app_purchase_storekit/shared/Classes/InAppPurchasePlugin.h rename to packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.h diff --git a/packages/in_app_purchase/in_app_purchase_storekit/shared/Classes/InAppPurchasePlugin.m b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.m similarity index 100% rename from packages/in_app_purchase/in_app_purchase_storekit/shared/Classes/InAppPurchasePlugin.m rename to packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.m diff --git a/packages/in_app_purchase/in_app_purchase_storekit/shared/in_app_purchase_storekit.podspec b/packages/in_app_purchase/in_app_purchase_storekit/darwin/in_app_purchase_storekit.podspec similarity index 100% rename from packages/in_app_purchase/in_app_purchase_storekit/shared/in_app_purchase_storekit.podspec rename to packages/in_app_purchase/in_app_purchase_storekit/darwin/in_app_purchase_storekit.podspec diff --git a/packages/in_app_purchase/in_app_purchase_storekit/ios/Assets/.gitkeep b/packages/in_app_purchase/in_app_purchase_storekit/ios/Assets/.gitkeep deleted file mode 120000 index bf2007784034..000000000000 --- a/packages/in_app_purchase/in_app_purchase_storekit/ios/Assets/.gitkeep +++ /dev/null @@ -1 +0,0 @@ -../../shared/Assets/.gitkeep \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAObjectTranslator.h b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAObjectTranslator.h index 8c80f07ea9a6..6b974bc7d268 120000 --- a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAObjectTranslator.h +++ b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAObjectTranslator.h @@ -1 +1 @@ -../../shared/Classes/FIAObjectTranslator.h \ No newline at end of file +../../darwin/Classes/FIAObjectTranslator.h \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAObjectTranslator.m b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAObjectTranslator.m index 643df24599b8..f9b4ffe6732d 120000 --- a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAObjectTranslator.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAObjectTranslator.m @@ -1 +1 @@ -../../shared/Classes/FIAObjectTranslator.m \ No newline at end of file +../../darwin/Classes/FIAObjectTranslator.m \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPPaymentQueueDelegate.h b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPPaymentQueueDelegate.h index 5e54d74d187a..e4b452397bc2 120000 --- a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPPaymentQueueDelegate.h +++ b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPPaymentQueueDelegate.h @@ -1 +1 @@ -../../shared/Classes/FIAPPaymentQueueDelegate.h \ No newline at end of file +../../darwin/Classes/FIAPPaymentQueueDelegate.h \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPPaymentQueueDelegate.m b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPPaymentQueueDelegate.m index f972e7d7c7e8..a1b95ef97c1b 120000 --- a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPPaymentQueueDelegate.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPPaymentQueueDelegate.m @@ -1 +1 @@ -../../shared/Classes/FIAPPaymentQueueDelegate.m \ No newline at end of file +../../darwin/Classes/FIAPPaymentQueueDelegate.m \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPReceiptManager.h b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPReceiptManager.h index f5c64da51bf3..88f02af0b00a 120000 --- a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPReceiptManager.h +++ b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPReceiptManager.h @@ -1 +1 @@ -../../shared/Classes/FIAPReceiptManager.h \ No newline at end of file +../../darwin/Classes/FIAPReceiptManager.h \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPReceiptManager.m b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPReceiptManager.m index 7cc0593abb34..f303c3c162a0 120000 --- a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPReceiptManager.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPReceiptManager.m @@ -1 +1 @@ -../../shared/Classes/FIAPReceiptManager.m \ No newline at end of file +../../darwin/Classes/FIAPReceiptManager.m \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPRequestHandler.h b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPRequestHandler.h index b008c38df4bb..9eb31f26b048 120000 --- a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPRequestHandler.h +++ b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPRequestHandler.h @@ -1 +1 @@ -../../shared/Classes/FIAPRequestHandler.h \ No newline at end of file +../../darwin/Classes/FIAPRequestHandler.h \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPRequestHandler.m b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPRequestHandler.m index 22a1ba3a7c48..d6976dc0dd26 120000 --- a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPRequestHandler.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPRequestHandler.m @@ -1 +1 @@ -../../shared/Classes/FIAPRequestHandler.m \ No newline at end of file +../../darwin/Classes/FIAPRequestHandler.m \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPaymentQueueHandler.h b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPaymentQueueHandler.h index 8a64356be52e..6bc9c2f6dc85 120000 --- a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPaymentQueueHandler.h +++ b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPaymentQueueHandler.h @@ -1 +1 @@ -../../shared/Classes/FIAPaymentQueueHandler.h \ No newline at end of file +../../darwin/Classes/FIAPaymentQueueHandler.h \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPaymentQueueHandler.m b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPaymentQueueHandler.m index 87359d2e1c55..8c892d29f1e6 120000 --- a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPaymentQueueHandler.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPaymentQueueHandler.m @@ -1 +1 @@ -../../shared/Classes/FIAPaymentQueueHandler.m \ No newline at end of file +../../darwin/Classes/FIAPaymentQueueHandler.m \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIATransactionCache.h b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIATransactionCache.h index 1f8f3f92da93..8862d80dde39 120000 --- a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIATransactionCache.h +++ b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIATransactionCache.h @@ -1 +1 @@ -../../shared/Classes/FIATransactionCache.h \ No newline at end of file +../../darwin/Classes/FIATransactionCache.h \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIATransactionCache.m b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIATransactionCache.m index b27e9811319e..8c0dd87c7e97 120000 --- a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIATransactionCache.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIATransactionCache.m @@ -1 +1 @@ -../../shared/Classes/FIATransactionCache.m \ No newline at end of file +../../darwin/Classes/FIATransactionCache.m \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/InAppPurchasePlugin.h b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/InAppPurchasePlugin.h index d92777687ecd..0ec6c66d54f8 120000 --- a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/InAppPurchasePlugin.h +++ b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/InAppPurchasePlugin.h @@ -1 +1 @@ -../../shared/Classes/InAppPurchasePlugin.h \ No newline at end of file +../../darwin/Classes/InAppPurchasePlugin.h \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/InAppPurchasePlugin.m b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/InAppPurchasePlugin.m index 67f61aad1fb0..e087d55187e8 120000 --- a/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/InAppPurchasePlugin.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/InAppPurchasePlugin.m @@ -1 +1 @@ -../../shared/Classes/InAppPurchasePlugin.m \ No newline at end of file +../../darwin/Classes/InAppPurchasePlugin.m \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/ios/in_app_purchase_storekit.podspec b/packages/in_app_purchase/in_app_purchase_storekit/ios/in_app_purchase_storekit.podspec index 79982cb307de..4157364db8d6 120000 --- a/packages/in_app_purchase/in_app_purchase_storekit/ios/in_app_purchase_storekit.podspec +++ b/packages/in_app_purchase/in_app_purchase_storekit/ios/in_app_purchase_storekit.podspec @@ -1 +1 @@ -../shared/in_app_purchase_storekit.podspec \ No newline at end of file +../darwin/in_app_purchase_storekit.podspec \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/macos/Assets/.gitkeep b/packages/in_app_purchase/in_app_purchase_storekit/macos/Assets/.gitkeep deleted file mode 120000 index bf2007784034..000000000000 --- a/packages/in_app_purchase/in_app_purchase_storekit/macos/Assets/.gitkeep +++ /dev/null @@ -1 +0,0 @@ -../../shared/Assets/.gitkeep \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAObjectTranslator.h b/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAObjectTranslator.h index 8c80f07ea9a6..6b974bc7d268 120000 --- a/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAObjectTranslator.h +++ b/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAObjectTranslator.h @@ -1 +1 @@ -../../shared/Classes/FIAObjectTranslator.h \ No newline at end of file +../../darwin/Classes/FIAObjectTranslator.h \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAObjectTranslator.m b/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAObjectTranslator.m index 643df24599b8..f9b4ffe6732d 120000 --- a/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAObjectTranslator.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAObjectTranslator.m @@ -1 +1 @@ -../../shared/Classes/FIAObjectTranslator.m \ No newline at end of file +../../darwin/Classes/FIAObjectTranslator.m \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAPPaymentQueueDelegate.h b/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAPPaymentQueueDelegate.h index 5e54d74d187a..e4b452397bc2 120000 --- a/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAPPaymentQueueDelegate.h +++ b/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAPPaymentQueueDelegate.h @@ -1 +1 @@ -../../shared/Classes/FIAPPaymentQueueDelegate.h \ No newline at end of file +../../darwin/Classes/FIAPPaymentQueueDelegate.h \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAPPaymentQueueDelegate.m b/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAPPaymentQueueDelegate.m index f972e7d7c7e8..a1b95ef97c1b 120000 --- a/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAPPaymentQueueDelegate.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAPPaymentQueueDelegate.m @@ -1 +1 @@ -../../shared/Classes/FIAPPaymentQueueDelegate.m \ No newline at end of file +../../darwin/Classes/FIAPPaymentQueueDelegate.m \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAPReceiptManager.h b/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAPReceiptManager.h index f5c64da51bf3..88f02af0b00a 120000 --- a/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAPReceiptManager.h +++ b/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAPReceiptManager.h @@ -1 +1 @@ -../../shared/Classes/FIAPReceiptManager.h \ No newline at end of file +../../darwin/Classes/FIAPReceiptManager.h \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAPReceiptManager.m b/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAPReceiptManager.m index 7cc0593abb34..f303c3c162a0 120000 --- a/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAPReceiptManager.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAPReceiptManager.m @@ -1 +1 @@ -../../shared/Classes/FIAPReceiptManager.m \ No newline at end of file +../../darwin/Classes/FIAPReceiptManager.m \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAPRequestHandler.h b/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAPRequestHandler.h index b008c38df4bb..9eb31f26b048 120000 --- a/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAPRequestHandler.h +++ b/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAPRequestHandler.h @@ -1 +1 @@ -../../shared/Classes/FIAPRequestHandler.h \ No newline at end of file +../../darwin/Classes/FIAPRequestHandler.h \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAPRequestHandler.m b/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAPRequestHandler.m index 22a1ba3a7c48..d6976dc0dd26 120000 --- a/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAPRequestHandler.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAPRequestHandler.m @@ -1 +1 @@ -../../shared/Classes/FIAPRequestHandler.m \ No newline at end of file +../../darwin/Classes/FIAPRequestHandler.m \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAPaymentQueueHandler.h b/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAPaymentQueueHandler.h index 8a64356be52e..6bc9c2f6dc85 120000 --- a/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAPaymentQueueHandler.h +++ b/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAPaymentQueueHandler.h @@ -1 +1 @@ -../../shared/Classes/FIAPaymentQueueHandler.h \ No newline at end of file +../../darwin/Classes/FIAPaymentQueueHandler.h \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAPaymentQueueHandler.m b/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAPaymentQueueHandler.m index 87359d2e1c55..8c892d29f1e6 120000 --- a/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAPaymentQueueHandler.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIAPaymentQueueHandler.m @@ -1 +1 @@ -../../shared/Classes/FIAPaymentQueueHandler.m \ No newline at end of file +../../darwin/Classes/FIAPaymentQueueHandler.m \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIATransactionCache.h b/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIATransactionCache.h index 1f8f3f92da93..8862d80dde39 120000 --- a/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIATransactionCache.h +++ b/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIATransactionCache.h @@ -1 +1 @@ -../../shared/Classes/FIATransactionCache.h \ No newline at end of file +../../darwin/Classes/FIATransactionCache.h \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIATransactionCache.m b/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIATransactionCache.m index b27e9811319e..8c0dd87c7e97 120000 --- a/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIATransactionCache.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/FIATransactionCache.m @@ -1 +1 @@ -../../shared/Classes/FIATransactionCache.m \ No newline at end of file +../../darwin/Classes/FIATransactionCache.m \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/InAppPurchasePlugin.h b/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/InAppPurchasePlugin.h index d92777687ecd..0ec6c66d54f8 120000 --- a/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/InAppPurchasePlugin.h +++ b/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/InAppPurchasePlugin.h @@ -1 +1 @@ -../../shared/Classes/InAppPurchasePlugin.h \ No newline at end of file +../../darwin/Classes/InAppPurchasePlugin.h \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/InAppPurchasePlugin.m b/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/InAppPurchasePlugin.m index 67f61aad1fb0..e087d55187e8 120000 --- a/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/InAppPurchasePlugin.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/macos/Classes/InAppPurchasePlugin.m @@ -1 +1 @@ -../../shared/Classes/InAppPurchasePlugin.m \ No newline at end of file +../../darwin/Classes/InAppPurchasePlugin.m \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/macos/in_app_purchase_storekit.podspec b/packages/in_app_purchase/in_app_purchase_storekit/macos/in_app_purchase_storekit.podspec index 79982cb307de..4157364db8d6 120000 --- a/packages/in_app_purchase/in_app_purchase_storekit/macos/in_app_purchase_storekit.podspec +++ b/packages/in_app_purchase/in_app_purchase_storekit/macos/in_app_purchase_storekit.podspec @@ -1 +1 @@ -../shared/in_app_purchase_storekit.podspec \ No newline at end of file +../darwin/in_app_purchase_storekit.podspec \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml b/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml index a4e553ab7c2c..3a4b0fc04f94 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml +++ b/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml @@ -2,7 +2,7 @@ name: in_app_purchase_storekit description: An implementation for the iOS and macOS platforms of the Flutter `in_app_purchase` plugin. This uses the StoreKit Framework. repository: https://github.com/flutter/plugins/tree/main/packages/in_app_purchase/in_app_purchase_storekit issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+in_app_purchase%22 -version: 0.3.5 +version: 0.3.5+1 environment: sdk: ">=2.14.0 <3.0.0" @@ -14,8 +14,10 @@ flutter: platforms: ios: pluginClass: InAppPurchasePlugin + sharedDarwinSource: true macos: pluginClass: InAppPurchasePlugin + sharedDarwinSource: true dependencies: collection: ^1.15.0 diff --git a/packages/in_app_purchase/in_app_purchase_storekit/shared/Assets/.gitkeep b/packages/in_app_purchase/in_app_purchase_storekit/shared/Assets/.gitkeep deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/packages/shared_preferences/shared_preferences_foundation/CHANGELOG.md b/packages/shared_preferences/shared_preferences_foundation/CHANGELOG.md index 8b454e7a0236..b178143ca0b8 100644 --- a/packages/shared_preferences/shared_preferences_foundation/CHANGELOG.md +++ b/packages/shared_preferences/shared_preferences_foundation/CHANGELOG.md @@ -1,5 +1,6 @@ -## NEXT +## 2.1.3 +* Uses the new `sharedDarwinSource` flag when available. * Updates minimum Flutter version to 3.0. ## 2.1.2 diff --git a/packages/shared_preferences/shared_preferences_foundation/pubspec.yaml b/packages/shared_preferences/shared_preferences_foundation/pubspec.yaml index caacacaeb52a..3deb07fc5960 100644 --- a/packages/shared_preferences/shared_preferences_foundation/pubspec.yaml +++ b/packages/shared_preferences/shared_preferences_foundation/pubspec.yaml @@ -2,7 +2,7 @@ name: shared_preferences_foundation description: iOS and macOS implementation of the shared_preferences plugin. repository: https://github.com/flutter/plugins/tree/main/packages/shared_preferences/shared_preferences_foundation issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+shared_preferences%22 -version: 2.1.2 +version: 2.1.3 environment: sdk: ">=2.12.0 <3.0.0" @@ -12,14 +12,14 @@ flutter: plugin: implements: shared_preferences platforms: - # TODO(stuartmorgan): Add sharedDarwinSource to these once - # https://github.com/flutter/flutter/pull/115337 lands. ios: pluginClass: SharedPreferencesPlugin dartPluginClass: SharedPreferencesFoundation + sharedDarwinSource: true macos: pluginClass: SharedPreferencesPlugin dartPluginClass: SharedPreferencesFoundation + sharedDarwinSource: true dependencies: flutter: From 8f83dc29be84603e32f8335abfb1e4bc1dc5f953 Mon Sep 17 00:00:00 2001 From: stuartmorgan Date: Tue, 31 Jan 2023 09:37:45 -0800 Subject: [PATCH 056/130] [various] Standardize the extension for Pigeon-generated Dart (#7029) * Standardize on .g.dart * Remove unused exclusion patterns * Mark pigeons/ as dev-only in the tooling * Version bumps * Add missed files * More new import fixes --- analysis_options.yaml | 2 -- .../lib/src/android_camera_camerax_flutter_api_impls.dart | 2 +- packages/camera/camera_android_camerax/lib/src/camera.dart | 2 +- .../camera/camera_android_camerax/lib/src/camera_info.dart | 2 +- .../camera_android_camerax/lib/src/camera_selector.dart | 2 +- .../{camerax_library.pigeon.dart => camerax_library.g.dart} | 0 .../camera/camera_android_camerax/lib/src/java_object.dart | 2 +- .../lib/src/process_camera_provider.dart | 2 +- .../camera_android_camerax/lib/src/system_services.dart | 2 +- .../camera_android_camerax/pigeons/camerax_library.dart | 4 ++-- .../camera/camera_android_camerax/test/camera_info_test.dart | 4 ++-- .../camera_android_camerax/test/camera_info_test.mocks.dart | 2 +- .../camera_android_camerax/test/camera_selector_test.dart | 4 ++-- .../test/camera_selector_test.mocks.dart | 2 +- .../test/process_camera_provider_test.dart | 2 +- .../test/process_camera_provider_test.mocks.dart | 2 +- .../camera_android_camerax/test/system_services_test.dart | 4 ++-- .../test/system_services_test.mocks.dart | 4 ++-- ...merax_library.pigeon.dart => test_camerax_library.g.dart} | 2 +- .../file_selector/file_selector_ios/pigeons/messages.dart | 2 +- .../file_selector_ios/test/file_selector_ios_test.dart | 2 +- .../file_selector_ios/test/file_selector_ios_test.mocks.dart | 2 +- .../test/{test_api.dart => test_api.g.dart} | 0 .../file_selector_windows/pigeons/messages.dart | 2 +- .../test/file_selector_windows_test.dart | 2 +- .../test/file_selector_windows_test.mocks.dart | 2 +- .../test/{test_api.dart => test_api.g.dart} | 0 packages/image_picker/image_picker_ios/pigeons/messages.dart | 2 +- .../image_picker_ios/test/image_picker_ios_test.dart | 2 +- .../image_picker_ios/test/{test_api.dart => test_api.g.dart} | 0 .../video_player/video_player_android/pigeons/messages.dart | 2 +- .../video_player_android/test/android_video_player_test.dart | 2 +- .../test/{test_api.dart => test_api.g.dart} | 0 .../video_player_avfoundation/pigeons/messages.dart | 2 +- .../test/avfoundation_video_player_test.dart | 2 +- .../test/{test_api.dart => test_api.g.dart} | 0 .../webview_flutter/webview_flutter_android/CHANGELOG.md | 4 ++++ .../webview_flutter_android/lib/src/android_webview.dart | 2 +- .../{android_webview.pigeon.dart => android_webview.g.dart} | 0 .../lib/src/android_webview_api_impls.dart | 4 ++-- .../webview_flutter_android/pigeons/android_webview.dart | 4 ++-- .../webview_flutter/webview_flutter_android/pubspec.yaml | 2 +- .../webview_flutter_android/test/android_webview_test.dart | 4 ++-- .../test/android_webview_test.mocks.dart | 4 ++-- .../test/legacy/webview_android_widget_test.dart | 2 +- ...droid_webview.pigeon.dart => test_android_webview.g.dart} | 2 +- .../webview_flutter/webview_flutter_wkwebview/CHANGELOG.md | 4 ++++ .../lib/src/common/{web_kit.pigeon.dart => web_kit.g.dart} | 0 .../lib/src/foundation/foundation_api_impls.dart | 2 +- .../lib/src/ui_kit/ui_kit_api_impls.dart | 2 +- .../lib/src/web_kit/web_kit_api_impls.dart | 4 ++-- .../webview_flutter_wkwebview/pigeons/web_kit.dart | 4 ++-- .../webview_flutter/webview_flutter_wkwebview/pubspec.yaml | 2 +- .../common/{test_web_kit.pigeon.dart => test_web_kit.g.dart} | 2 +- .../test/src/foundation/foundation_test.dart | 4 ++-- .../test/src/foundation/foundation_test.mocks.dart | 5 ++--- .../test/src/ui_kit/ui_kit_test.dart | 2 +- .../test/src/ui_kit/ui_kit_test.mocks.dart | 5 ++--- .../test/src/web_kit/web_kit_test.dart | 4 ++-- .../test/src/web_kit/web_kit_test.mocks.dart | 5 ++--- script/tool/lib/src/common/package_state_utils.dart | 3 +++ script/tool/test/common/package_state_utils_test.dart | 2 ++ 62 files changed, 78 insertions(+), 70 deletions(-) rename packages/camera/camera_android_camerax/lib/src/{camerax_library.pigeon.dart => camerax_library.g.dart} (100%) rename packages/camera/camera_android_camerax/test/{test_camerax_library.pigeon.dart => test_camerax_library.g.dart} (99%) rename packages/file_selector/file_selector_ios/test/{test_api.dart => test_api.g.dart} (100%) rename packages/file_selector/file_selector_windows/test/{test_api.dart => test_api.g.dart} (100%) rename packages/image_picker/image_picker_ios/test/{test_api.dart => test_api.g.dart} (100%) rename packages/video_player/video_player_android/test/{test_api.dart => test_api.g.dart} (100%) rename packages/video_player/video_player_avfoundation/test/{test_api.dart => test_api.g.dart} (100%) rename packages/webview_flutter/webview_flutter_android/lib/src/{android_webview.pigeon.dart => android_webview.g.dart} (100%) rename packages/webview_flutter/webview_flutter_android/test/{test_android_webview.pigeon.dart => test_android_webview.g.dart} (99%) rename packages/webview_flutter/webview_flutter_wkwebview/lib/src/common/{web_kit.pigeon.dart => web_kit.g.dart} (100%) rename packages/webview_flutter/webview_flutter_wkwebview/test/src/common/{test_web_kit.pigeon.dart => test_web_kit.g.dart} (99%) diff --git a/analysis_options.yaml b/analysis_options.yaml index 1eb0d232ce1b..3f12e0bf7038 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -47,9 +47,7 @@ analyzer: exclude: # Ignore generated files - '**/*.g.dart' - - 'lib/src/generated/*.dart' - '**/*.mocks.dart' # Mockito @GenerateMocks - - '**/*.pigeon.dart' # Pigeon generated file linter: rules: diff --git a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax_flutter_api_impls.dart b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax_flutter_api_impls.dart index 2895a38e4132..0a1b3ce3b285 100644 --- a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax_flutter_api_impls.dart +++ b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax_flutter_api_impls.dart @@ -5,7 +5,7 @@ import 'camera.dart'; import 'camera_info.dart'; import 'camera_selector.dart'; -import 'camerax_library.pigeon.dart'; +import 'camerax_library.g.dart'; import 'java_object.dart'; import 'process_camera_provider.dart'; import 'system_services.dart'; diff --git a/packages/camera/camera_android_camerax/lib/src/camera.dart b/packages/camera/camera_android_camerax/lib/src/camera.dart index 0a3820cd0248..24ff30540b28 100644 --- a/packages/camera/camera_android_camerax/lib/src/camera.dart +++ b/packages/camera/camera_android_camerax/lib/src/camera.dart @@ -5,7 +5,7 @@ import 'package:flutter/services.dart' show BinaryMessenger; import 'android_camera_camerax_flutter_api_impls.dart'; -import 'camerax_library.pigeon.dart'; +import 'camerax_library.g.dart'; import 'instance_manager.dart'; import 'java_object.dart'; diff --git a/packages/camera/camera_android_camerax/lib/src/camera_info.dart b/packages/camera/camera_android_camerax/lib/src/camera_info.dart index d03426f40027..8c2c7bcf0aec 100644 --- a/packages/camera/camera_android_camerax/lib/src/camera_info.dart +++ b/packages/camera/camera_android_camerax/lib/src/camera_info.dart @@ -5,7 +5,7 @@ import 'package:flutter/services.dart' show BinaryMessenger; import 'android_camera_camerax_flutter_api_impls.dart'; -import 'camerax_library.pigeon.dart'; +import 'camerax_library.g.dart'; import 'instance_manager.dart'; import 'java_object.dart'; diff --git a/packages/camera/camera_android_camerax/lib/src/camera_selector.dart b/packages/camera/camera_android_camerax/lib/src/camera_selector.dart index 094147f208fe..43a1dabd6906 100644 --- a/packages/camera/camera_android_camerax/lib/src/camera_selector.dart +++ b/packages/camera/camera_android_camerax/lib/src/camera_selector.dart @@ -6,7 +6,7 @@ import 'package:flutter/services.dart'; import 'android_camera_camerax_flutter_api_impls.dart'; import 'camera_info.dart'; -import 'camerax_library.pigeon.dart'; +import 'camerax_library.g.dart'; import 'instance_manager.dart'; import 'java_object.dart'; diff --git a/packages/camera/camera_android_camerax/lib/src/camerax_library.pigeon.dart b/packages/camera/camera_android_camerax/lib/src/camerax_library.g.dart similarity index 100% rename from packages/camera/camera_android_camerax/lib/src/camerax_library.pigeon.dart rename to packages/camera/camera_android_camerax/lib/src/camerax_library.g.dart diff --git a/packages/camera/camera_android_camerax/lib/src/java_object.dart b/packages/camera/camera_android_camerax/lib/src/java_object.dart index 36a29ed0517b..f6127d4a8106 100644 --- a/packages/camera/camera_android_camerax/lib/src/java_object.dart +++ b/packages/camera/camera_android_camerax/lib/src/java_object.dart @@ -5,7 +5,7 @@ import 'package:flutter/foundation.dart' show immutable; import 'package:flutter/services.dart'; -import 'camerax_library.pigeon.dart'; +import 'camerax_library.g.dart'; import 'instance_manager.dart'; /// Root of the Java class hierarchy. diff --git a/packages/camera/camera_android_camerax/lib/src/process_camera_provider.dart b/packages/camera/camera_android_camerax/lib/src/process_camera_provider.dart index 30c8162654ad..ed9e820a1fa0 100644 --- a/packages/camera/camera_android_camerax/lib/src/process_camera_provider.dart +++ b/packages/camera/camera_android_camerax/lib/src/process_camera_provider.dart @@ -8,7 +8,7 @@ import 'android_camera_camerax_flutter_api_impls.dart'; import 'camera.dart'; import 'camera_info.dart'; import 'camera_selector.dart'; -import 'camerax_library.pigeon.dart'; +import 'camerax_library.g.dart'; import 'instance_manager.dart'; import 'java_object.dart'; import 'use_case.dart'; diff --git a/packages/camera/camera_android_camerax/lib/src/system_services.dart b/packages/camera/camera_android_camerax/lib/src/system_services.dart index d5fbe5a92c91..bc6477e0dcb8 100644 --- a/packages/camera/camera_android_camerax/lib/src/system_services.dart +++ b/packages/camera/camera_android_camerax/lib/src/system_services.dart @@ -9,7 +9,7 @@ import 'package:camera_platform_interface/camera_platform_interface.dart' import 'package:flutter/services.dart'; import 'android_camera_camerax_flutter_api_impls.dart'; -import 'camerax_library.pigeon.dart'; +import 'camerax_library.g.dart'; // Ignoring lint indicating this class only contains static members // as this class is a wrapper for various Android system services. diff --git a/packages/camera/camera_android_camerax/pigeons/camerax_library.dart b/packages/camera/camera_android_camerax/pigeons/camerax_library.dart index 5e804a225eda..7fce6ce329fd 100644 --- a/packages/camera/camera_android_camerax/pigeons/camerax_library.dart +++ b/packages/camera/camera_android_camerax/pigeons/camerax_library.dart @@ -6,8 +6,8 @@ import 'package:pigeon/pigeon.dart'; @ConfigurePigeon( PigeonOptions( - dartOut: 'lib/src/camerax_library.pigeon.dart', - dartTestOut: 'test/test_camerax_library.pigeon.dart', + dartOut: 'lib/src/camerax_library.g.dart', + dartTestOut: 'test/test_camerax_library.g.dart', dartOptions: DartOptions(copyrightHeader: [ 'Copyright 2013 The Flutter Authors. All rights reserved.', 'Use of this source code is governed by a BSD-style license that can be', diff --git a/packages/camera/camera_android_camerax/test/camera_info_test.dart b/packages/camera/camera_android_camerax/test/camera_info_test.dart index eda822b33f73..852c799ebfbe 100644 --- a/packages/camera/camera_android_camerax/test/camera_info_test.dart +++ b/packages/camera/camera_android_camerax/test/camera_info_test.dart @@ -3,14 +3,14 @@ // found in the LICENSE file. import 'package:camera_android_camerax/src/camera_info.dart'; -import 'package:camera_android_camerax/src/camerax_library.pigeon.dart'; +import 'package:camera_android_camerax/src/camerax_library.g.dart'; import 'package:camera_android_camerax/src/instance_manager.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'camera_info_test.mocks.dart'; -import 'test_camerax_library.pigeon.dart'; +import 'test_camerax_library.g.dart'; @GenerateMocks([TestCameraInfoHostApi]) void main() { diff --git a/packages/camera/camera_android_camerax/test/camera_info_test.mocks.dart b/packages/camera/camera_android_camerax/test/camera_info_test.mocks.dart index 63ec03c30083..5e558a8226b6 100644 --- a/packages/camera/camera_android_camerax/test/camera_info_test.mocks.dart +++ b/packages/camera/camera_android_camerax/test/camera_info_test.mocks.dart @@ -5,7 +5,7 @@ // ignore_for_file: no_leading_underscores_for_library_prefixes import 'package:mockito/mockito.dart' as _i1; -import 'test_camerax_library.pigeon.dart' as _i2; +import 'test_camerax_library.g.dart' as _i2; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values diff --git a/packages/camera/camera_android_camerax/test/camera_selector_test.dart b/packages/camera/camera_android_camerax/test/camera_selector_test.dart index c4ccd6262376..54f7864fb85f 100644 --- a/packages/camera/camera_android_camerax/test/camera_selector_test.dart +++ b/packages/camera/camera_android_camerax/test/camera_selector_test.dart @@ -4,14 +4,14 @@ import 'package:camera_android_camerax/src/camera_info.dart'; import 'package:camera_android_camerax/src/camera_selector.dart'; -import 'package:camera_android_camerax/src/camerax_library.pigeon.dart'; +import 'package:camera_android_camerax/src/camerax_library.g.dart'; import 'package:camera_android_camerax/src/instance_manager.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'camera_selector_test.mocks.dart'; -import 'test_camerax_library.pigeon.dart'; +import 'test_camerax_library.g.dart'; @GenerateMocks([TestCameraSelectorHostApi]) void main() { diff --git a/packages/camera/camera_android_camerax/test/camera_selector_test.mocks.dart b/packages/camera/camera_android_camerax/test/camera_selector_test.mocks.dart index bb08c82514a5..31dce5177e2d 100644 --- a/packages/camera/camera_android_camerax/test/camera_selector_test.mocks.dart +++ b/packages/camera/camera_android_camerax/test/camera_selector_test.mocks.dart @@ -5,7 +5,7 @@ // ignore_for_file: no_leading_underscores_for_library_prefixes import 'package:mockito/mockito.dart' as _i1; -import 'test_camerax_library.pigeon.dart' as _i2; +import 'test_camerax_library.g.dart' as _i2; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values diff --git a/packages/camera/camera_android_camerax/test/process_camera_provider_test.dart b/packages/camera/camera_android_camerax/test/process_camera_provider_test.dart index c4f56f677a58..548ac3e00d65 100644 --- a/packages/camera/camera_android_camerax/test/process_camera_provider_test.dart +++ b/packages/camera/camera_android_camerax/test/process_camera_provider_test.dart @@ -13,7 +13,7 @@ import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'process_camera_provider_test.mocks.dart'; -import 'test_camerax_library.pigeon.dart'; +import 'test_camerax_library.g.dart'; @GenerateMocks([TestProcessCameraProviderHostApi]) void main() { diff --git a/packages/camera/camera_android_camerax/test/process_camera_provider_test.mocks.dart b/packages/camera/camera_android_camerax/test/process_camera_provider_test.mocks.dart index 7b0ca76dd1a0..2ce4ab72fa57 100644 --- a/packages/camera/camera_android_camerax/test/process_camera_provider_test.mocks.dart +++ b/packages/camera/camera_android_camerax/test/process_camera_provider_test.mocks.dart @@ -7,7 +7,7 @@ import 'dart:async' as _i3; import 'package:mockito/mockito.dart' as _i1; -import 'test_camerax_library.pigeon.dart' as _i2; +import 'test_camerax_library.g.dart' as _i2; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values diff --git a/packages/camera/camera_android_camerax/test/system_services_test.dart b/packages/camera/camera_android_camerax/test/system_services_test.dart index c8a2721ecc2d..2d2cea6d9190 100644 --- a/packages/camera/camera_android_camerax/test/system_services_test.dart +++ b/packages/camera/camera_android_camerax/test/system_services_test.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:camera_android_camerax/src/camerax_library.pigeon.dart' +import 'package:camera_android_camerax/src/camerax_library.g.dart' show CameraPermissionsErrorData; import 'package:camera_android_camerax/src/system_services.dart'; import 'package:camera_platform_interface/camera_platform_interface.dart' @@ -13,7 +13,7 @@ import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'system_services_test.mocks.dart'; -import 'test_camerax_library.pigeon.dart'; +import 'test_camerax_library.g.dart'; @GenerateMocks([TestSystemServicesHostApi]) void main() { diff --git a/packages/camera/camera_android_camerax/test/system_services_test.mocks.dart b/packages/camera/camera_android_camerax/test/system_services_test.mocks.dart index 2698d2d52307..0963ffb26a2a 100644 --- a/packages/camera/camera_android_camerax/test/system_services_test.mocks.dart +++ b/packages/camera/camera_android_camerax/test/system_services_test.mocks.dart @@ -5,10 +5,10 @@ // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:async' as _i3; -import 'package:camera_android_camerax/src/camerax_library.pigeon.dart' as _i4; +import 'package:camera_android_camerax/src/camerax_library.g.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; -import 'test_camerax_library.pigeon.dart' as _i2; +import 'test_camerax_library.g.dart' as _i2; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values diff --git a/packages/camera/camera_android_camerax/test/test_camerax_library.pigeon.dart b/packages/camera/camera_android_camerax/test/test_camerax_library.g.dart similarity index 99% rename from packages/camera/camera_android_camerax/test/test_camerax_library.pigeon.dart rename to packages/camera/camera_android_camerax/test/test_camerax_library.g.dart index 349004c7e6de..55f2c5e3e6a6 100644 --- a/packages/camera/camera_android_camerax/test/test_camerax_library.pigeon.dart +++ b/packages/camera/camera_android_camerax/test/test_camerax_library.g.dart @@ -11,7 +11,7 @@ import 'package:flutter/foundation.dart' show WriteBuffer, ReadBuffer; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:camera_android_camerax/src/camerax_library.pigeon.dart'; +import 'package:camera_android_camerax/src/camerax_library.g.dart'; class _TestJavaObjectHostApiCodec extends StandardMessageCodec { const _TestJavaObjectHostApiCodec(); diff --git a/packages/file_selector/file_selector_ios/pigeons/messages.dart b/packages/file_selector/file_selector_ios/pigeons/messages.dart index d0ea73cde111..66706cc2406e 100644 --- a/packages/file_selector/file_selector_ios/pigeons/messages.dart +++ b/packages/file_selector/file_selector_ios/pigeons/messages.dart @@ -6,7 +6,7 @@ import 'package:pigeon/pigeon.dart'; @ConfigurePigeon(PigeonOptions( dartOut: 'lib/src/messages.g.dart', - dartTestOut: 'test/test_api.dart', + dartTestOut: 'test/test_api.g.dart', objcHeaderOut: 'ios/Classes/messages.g.h', objcSourceOut: 'ios/Classes/messages.g.m', objcOptions: ObjcOptions( diff --git a/packages/file_selector/file_selector_ios/test/file_selector_ios_test.dart b/packages/file_selector/file_selector_ios/test/file_selector_ios_test.dart index f66bd7dc7ced..e10ad17a2fb4 100644 --- a/packages/file_selector/file_selector_ios/test/file_selector_ios_test.dart +++ b/packages/file_selector/file_selector_ios/test/file_selector_ios_test.dart @@ -11,7 +11,7 @@ import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'file_selector_ios_test.mocks.dart'; -import 'test_api.dart'; +import 'test_api.g.dart'; @GenerateMocks([TestFileSelectorApi]) void main() { diff --git a/packages/file_selector/file_selector_ios/test/file_selector_ios_test.mocks.dart b/packages/file_selector/file_selector_ios/test/file_selector_ios_test.mocks.dart index 38c91b46f65e..1d22ba75a10a 100644 --- a/packages/file_selector/file_selector_ios/test/file_selector_ios_test.mocks.dart +++ b/packages/file_selector/file_selector_ios/test/file_selector_ios_test.mocks.dart @@ -8,7 +8,7 @@ import 'dart:async' as _i3; import 'package:file_selector_ios/src/messages.g.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; -import 'test_api.dart' as _i2; +import 'test_api.g.dart' as _i2; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values diff --git a/packages/file_selector/file_selector_ios/test/test_api.dart b/packages/file_selector/file_selector_ios/test/test_api.g.dart similarity index 100% rename from packages/file_selector/file_selector_ios/test/test_api.dart rename to packages/file_selector/file_selector_ios/test/test_api.g.dart diff --git a/packages/file_selector/file_selector_windows/pigeons/messages.dart b/packages/file_selector/file_selector_windows/pigeons/messages.dart index f2c9ab71bd82..c3b3aff192b8 100644 --- a/packages/file_selector/file_selector_windows/pigeons/messages.dart +++ b/packages/file_selector/file_selector_windows/pigeons/messages.dart @@ -6,7 +6,7 @@ import 'package:pigeon/pigeon.dart'; @ConfigurePigeon(PigeonOptions( dartOut: 'lib/src/messages.g.dart', - dartTestOut: 'test/test_api.dart', + dartTestOut: 'test/test_api.g.dart', cppOptions: CppOptions(namespace: 'file_selector_windows'), cppHeaderOut: 'windows/messages.g.h', cppSourceOut: 'windows/messages.g.cpp', diff --git a/packages/file_selector/file_selector_windows/test/file_selector_windows_test.dart b/packages/file_selector/file_selector_windows/test/file_selector_windows_test.dart index f07c9b67618d..62745f7df707 100644 --- a/packages/file_selector/file_selector_windows/test/file_selector_windows_test.dart +++ b/packages/file_selector/file_selector_windows/test/file_selector_windows_test.dart @@ -11,7 +11,7 @@ import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'file_selector_windows_test.mocks.dart'; -import 'test_api.dart'; +import 'test_api.g.dart'; @GenerateMocks([TestFileSelectorApi]) void main() { diff --git a/packages/file_selector/file_selector_windows/test/file_selector_windows_test.mocks.dart b/packages/file_selector/file_selector_windows/test/file_selector_windows_test.mocks.dart index 61e17fcdfeaa..f60c92e6b7ee 100644 --- a/packages/file_selector/file_selector_windows/test/file_selector_windows_test.mocks.dart +++ b/packages/file_selector/file_selector_windows/test/file_selector_windows_test.mocks.dart @@ -5,7 +5,7 @@ import 'package:file_selector_windows/src/messages.g.dart' as _i3; import 'package:mockito/mockito.dart' as _i1; -import 'test_api.dart' as _i2; +import 'test_api.g.dart' as _i2; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values diff --git a/packages/file_selector/file_selector_windows/test/test_api.dart b/packages/file_selector/file_selector_windows/test/test_api.g.dart similarity index 100% rename from packages/file_selector/file_selector_windows/test/test_api.dart rename to packages/file_selector/file_selector_windows/test/test_api.g.dart diff --git a/packages/image_picker/image_picker_ios/pigeons/messages.dart b/packages/image_picker/image_picker_ios/pigeons/messages.dart index dd8a0f0c0834..d04841b0fde9 100644 --- a/packages/image_picker/image_picker_ios/pigeons/messages.dart +++ b/packages/image_picker/image_picker_ios/pigeons/messages.dart @@ -6,7 +6,7 @@ import 'package:pigeon/pigeon.dart'; @ConfigurePigeon(PigeonOptions( dartOut: 'lib/src/messages.g.dart', - dartTestOut: 'test/test_api.dart', + dartTestOut: 'test/test_api.g.dart', objcHeaderOut: 'ios/Classes/messages.g.h', objcSourceOut: 'ios/Classes/messages.g.m', objcOptions: ObjcOptions( diff --git a/packages/image_picker/image_picker_ios/test/image_picker_ios_test.dart b/packages/image_picker/image_picker_ios/test/image_picker_ios_test.dart index b20025770ad1..2c9d52509f26 100644 --- a/packages/image_picker/image_picker_ios/test/image_picker_ios_test.dart +++ b/packages/image_picker/image_picker_ios/test/image_picker_ios_test.dart @@ -8,7 +8,7 @@ import 'package:image_picker_ios/image_picker_ios.dart'; import 'package:image_picker_ios/src/messages.g.dart'; import 'package:image_picker_platform_interface/image_picker_platform_interface.dart'; -import 'test_api.dart'; +import 'test_api.g.dart'; @immutable class _LoggedMethodCall { diff --git a/packages/image_picker/image_picker_ios/test/test_api.dart b/packages/image_picker/image_picker_ios/test/test_api.g.dart similarity index 100% rename from packages/image_picker/image_picker_ios/test/test_api.dart rename to packages/image_picker/image_picker_ios/test/test_api.g.dart diff --git a/packages/video_player/video_player_android/pigeons/messages.dart b/packages/video_player/video_player_android/pigeons/messages.dart index bf552f9369df..90c9fbb61ea0 100644 --- a/packages/video_player/video_player_android/pigeons/messages.dart +++ b/packages/video_player/video_player_android/pigeons/messages.dart @@ -6,7 +6,7 @@ import 'package:pigeon/pigeon.dart'; @ConfigurePigeon(PigeonOptions( dartOut: 'lib/src/messages.g.dart', - dartTestOut: 'test/test_api.dart', + dartTestOut: 'test/test_api.g.dart', javaOut: 'android/src/main/java/io/flutter/plugins/videoplayer/Messages.java', javaOptions: JavaOptions( package: 'io.flutter.plugins.videoplayer', diff --git a/packages/video_player/video_player_android/test/android_video_player_test.dart b/packages/video_player/video_player_android/test/android_video_player_test.dart index fad9617ddad9..bca0d9d68e65 100644 --- a/packages/video_player/video_player_android/test/android_video_player_test.dart +++ b/packages/video_player/video_player_android/test/android_video_player_test.dart @@ -12,7 +12,7 @@ import 'package:video_player_android/src/messages.g.dart'; import 'package:video_player_android/video_player_android.dart'; import 'package:video_player_platform_interface/video_player_platform_interface.dart'; -import 'test_api.dart'; +import 'test_api.g.dart'; class _ApiLogger implements TestHostVideoPlayerApi { final List log = []; diff --git a/packages/video_player/video_player_android/test/test_api.dart b/packages/video_player/video_player_android/test/test_api.g.dart similarity index 100% rename from packages/video_player/video_player_android/test/test_api.dart rename to packages/video_player/video_player_android/test/test_api.g.dart diff --git a/packages/video_player/video_player_avfoundation/pigeons/messages.dart b/packages/video_player/video_player_avfoundation/pigeons/messages.dart index e6eda5960f29..695ff34e3ebd 100644 --- a/packages/video_player/video_player_avfoundation/pigeons/messages.dart +++ b/packages/video_player/video_player_avfoundation/pigeons/messages.dart @@ -6,7 +6,7 @@ import 'package:pigeon/pigeon.dart'; @ConfigurePigeon(PigeonOptions( dartOut: 'lib/src/messages.g.dart', - dartTestOut: 'test/test_api.dart', + dartTestOut: 'test/test_api.g.dart', objcHeaderOut: 'ios/Classes/messages.g.h', objcSourceOut: 'ios/Classes/messages.g.m', objcOptions: ObjcOptions( diff --git a/packages/video_player/video_player_avfoundation/test/avfoundation_video_player_test.dart b/packages/video_player/video_player_avfoundation/test/avfoundation_video_player_test.dart index ea81d438ad75..3367c9fc9ba6 100644 --- a/packages/video_player/video_player_avfoundation/test/avfoundation_video_player_test.dart +++ b/packages/video_player/video_player_avfoundation/test/avfoundation_video_player_test.dart @@ -12,7 +12,7 @@ import 'package:video_player_avfoundation/src/messages.g.dart'; import 'package:video_player_avfoundation/video_player_avfoundation.dart'; import 'package:video_player_platform_interface/video_player_platform_interface.dart'; -import 'test_api.dart'; +import 'test_api.g.dart'; class _ApiLogger implements TestHostVideoPlayerApi { final List log = []; diff --git a/packages/video_player/video_player_avfoundation/test/test_api.dart b/packages/video_player/video_player_avfoundation/test/test_api.g.dart similarity index 100% rename from packages/video_player/video_player_avfoundation/test/test_api.dart rename to packages/video_player/video_player_avfoundation/test/test_api.g.dart diff --git a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md index 1a490b5a579d..136d71485a0f 100644 --- a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## 3.2.4 + +* Renames Pigeon output files. + ## 3.2.3 * Fixes bug that prevented the web view from being garbage collected. diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart index c5d301d93202..1ab30a9ea1fd 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart @@ -16,7 +16,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart' show BinaryMessenger; import 'package:flutter/widgets.dart' show AndroidViewSurface; -import 'android_webview.pigeon.dart'; +import 'android_webview.g.dart'; import 'android_webview_api_impls.dart'; import 'instance_manager.dart'; diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.pigeon.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.g.dart similarity index 100% rename from packages/webview_flutter/webview_flutter_android/lib/src/android_webview.pigeon.dart rename to packages/webview_flutter/webview_flutter_android/lib/src/android_webview.g.dart diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_api_impls.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_api_impls.dart index 023f3c4ac421..127a2fa58ef8 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_api_impls.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_api_impls.dart @@ -10,10 +10,10 @@ import 'dart:ui'; import 'package:flutter/services.dart' show BinaryMessenger; import 'android_webview.dart'; -import 'android_webview.pigeon.dart'; +import 'android_webview.g.dart'; import 'instance_manager.dart'; -export 'android_webview.pigeon.dart' show FileChooserMode; +export 'android_webview.g.dart' show FileChooserMode; /// Converts [WebResourceRequestData] to [WebResourceRequest] WebResourceRequest _toWebResourceRequest(WebResourceRequestData data) { diff --git a/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart b/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart index 29d3181f0fbf..7f4d362c9273 100644 --- a/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart +++ b/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart @@ -6,8 +6,8 @@ import 'package:pigeon/pigeon.dart'; @ConfigurePigeon( PigeonOptions( - dartOut: 'lib/src/android_webview.pigeon.dart', - dartTestOut: 'test/test_android_webview.pigeon.dart', + dartOut: 'lib/src/android_webview.g.dart', + dartTestOut: 'test/test_android_webview.g.dart', dartOptions: DartOptions(copyrightHeader: [ 'Copyright 2013 The Flutter Authors. All rights reserved.', 'Use of this source code is governed by a BSD-style license that can be', diff --git a/packages/webview_flutter/webview_flutter_android/pubspec.yaml b/packages/webview_flutter/webview_flutter_android/pubspec.yaml index 30ea5d7823e9..d90844d9ce08 100644 --- a/packages/webview_flutter/webview_flutter_android/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_android/pubspec.yaml @@ -2,7 +2,7 @@ name: webview_flutter_android description: A Flutter plugin that provides a WebView widget on Android. repository: https://github.com/flutter/plugins/tree/main/packages/webview_flutter/webview_flutter_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22 -version: 3.2.3 +version: 3.2.4 environment: sdk: ">=2.17.0 <3.0.0" diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart index 9878d7a7e5cf..236d87da44eb 100644 --- a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart +++ b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart @@ -6,12 +6,12 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:webview_flutter_android/src/android_webview.dart'; -import 'package:webview_flutter_android/src/android_webview.pigeon.dart'; +import 'package:webview_flutter_android/src/android_webview.g.dart'; import 'package:webview_flutter_android/src/android_webview_api_impls.dart'; import 'package:webview_flutter_android/src/instance_manager.dart'; import 'android_webview_test.mocks.dart'; -import 'test_android_webview.pigeon.dart'; +import 'test_android_webview.g.dart'; @GenerateMocks([ CookieManagerHostApi, diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart index e3a2f108d8ec..0b5afbaf5b13 100644 --- a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart +++ b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart @@ -9,9 +9,9 @@ import 'dart:ui' as _i4; import 'package:mockito/mockito.dart' as _i1; import 'package:webview_flutter_android/src/android_webview.dart' as _i2; -import 'package:webview_flutter_android/src/android_webview.pigeon.dart' as _i3; +import 'package:webview_flutter_android/src/android_webview.g.dart' as _i3; -import 'test_android_webview.pigeon.dart' as _i6; +import 'test_android_webview.g.dart' as _i6; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values diff --git a/packages/webview_flutter/webview_flutter_android/test/legacy/webview_android_widget_test.dart b/packages/webview_flutter/webview_flutter_android/test/legacy/webview_android_widget_test.dart index 22c161afa180..44cc18510909 100644 --- a/packages/webview_flutter/webview_flutter_android/test/legacy/webview_android_widget_test.dart +++ b/packages/webview_flutter/webview_flutter_android/test/legacy/webview_android_widget_test.dart @@ -16,7 +16,7 @@ import 'package:webview_flutter_android/src/legacy/webview_android_widget.dart'; import 'package:webview_flutter_platform_interface/src/webview_flutter_platform_interface_legacy.dart'; import '../android_webview_test.mocks.dart' show MockTestWebViewHostApi; -import '../test_android_webview.pigeon.dart'; +import '../test_android_webview.g.dart'; import 'webview_android_widget_test.mocks.dart'; @GenerateMocks([ diff --git a/packages/webview_flutter/webview_flutter_android/test/test_android_webview.pigeon.dart b/packages/webview_flutter/webview_flutter_android/test/test_android_webview.g.dart similarity index 99% rename from packages/webview_flutter/webview_flutter_android/test/test_android_webview.pigeon.dart rename to packages/webview_flutter/webview_flutter_android/test/test_android_webview.g.dart index b5df3c1354d0..56ba79a66622 100644 --- a/packages/webview_flutter/webview_flutter_android/test/test_android_webview.pigeon.dart +++ b/packages/webview_flutter/webview_flutter_android/test/test_android_webview.g.dart @@ -11,7 +11,7 @@ import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:webview_flutter_android/src/android_webview.pigeon.dart'; +import 'package:webview_flutter_android/src/android_webview.g.dart'; /// Handles methods calls to the native Java Object class. /// diff --git a/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md b/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md index f79058c80216..d8442c2c1f0e 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md @@ -1,3 +1,7 @@ +## 3.0.5 + +* Renames Pigeon output files. + ## 3.0.4 * Fixes bug that prevented the web view from being garbage collected. diff --git a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/common/web_kit.pigeon.dart b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/common/web_kit.g.dart similarity index 100% rename from packages/webview_flutter/webview_flutter_wkwebview/lib/src/common/web_kit.pigeon.dart rename to packages/webview_flutter/webview_flutter_wkwebview/lib/src/common/web_kit.g.dart diff --git a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/foundation/foundation_api_impls.dart b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/foundation/foundation_api_impls.dart index d2310e0a5df8..445e232bb0ac 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/foundation/foundation_api_impls.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/foundation/foundation_api_impls.dart @@ -6,7 +6,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import '../common/instance_manager.dart'; -import '../common/web_kit.pigeon.dart'; +import '../common/web_kit.g.dart'; import 'foundation.dart'; Iterable diff --git a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/ui_kit/ui_kit_api_impls.dart b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/ui_kit/ui_kit_api_impls.dart index ae12a11820d8..4749c6afca3c 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/ui_kit/ui_kit_api_impls.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/ui_kit/ui_kit_api_impls.dart @@ -11,7 +11,7 @@ import 'package:flutter/painting.dart' show Color; import 'package:flutter/services.dart'; import '../common/instance_manager.dart'; -import '../common/web_kit.pigeon.dart'; +import '../common/web_kit.g.dart'; import '../foundation/foundation.dart'; import '../web_kit/web_kit.dart'; import 'ui_kit.dart'; diff --git a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/web_kit/web_kit_api_impls.dart b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/web_kit/web_kit_api_impls.dart index 97a3e0008f81..7cd29da3e716 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/web_kit/web_kit_api_impls.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/web_kit/web_kit_api_impls.dart @@ -6,11 +6,11 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import '../common/instance_manager.dart'; -import '../common/web_kit.pigeon.dart'; +import '../common/web_kit.g.dart'; import '../foundation/foundation.dart'; import 'web_kit.dart'; -export '../common/web_kit.pigeon.dart' show WKNavigationType; +export '../common/web_kit.g.dart' show WKNavigationType; Iterable _toWKWebsiteDataTypeEnumData( Iterable types) { diff --git a/packages/webview_flutter/webview_flutter_wkwebview/pigeons/web_kit.dart b/packages/webview_flutter/webview_flutter_wkwebview/pigeons/web_kit.dart index d32693ee5698..9b334c2411ff 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/pigeons/web_kit.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/pigeons/web_kit.dart @@ -6,8 +6,8 @@ import 'package:pigeon/pigeon.dart'; @ConfigurePigeon( PigeonOptions( - dartOut: 'lib/src/common/web_kit.pigeon.dart', - dartTestOut: 'test/src/common/test_web_kit.pigeon.dart', + dartOut: 'lib/src/common/web_kit.g.dart', + dartTestOut: 'test/src/common/test_web_kit.g.dart', dartOptions: DartOptions(copyrightHeader: [ 'Copyright 2013 The Flutter Authors. All rights reserved.', 'Use of this source code is governed by a BSD-style license that can be', diff --git a/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml b/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml index 15e8ac5678f8..5c4df9922840 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml @@ -2,7 +2,7 @@ name: webview_flutter_wkwebview description: A Flutter plugin that provides a WebView widget based on Apple's WKWebView control. repository: https://github.com/flutter/plugins/tree/main/packages/webview_flutter/webview_flutter_wkwebview issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22 -version: 3.0.4 +version: 3.0.5 environment: sdk: ">=2.17.0 <3.0.0" diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/src/common/test_web_kit.pigeon.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/src/common/test_web_kit.g.dart similarity index 99% rename from packages/webview_flutter/webview_flutter_wkwebview/test/src/common/test_web_kit.pigeon.dart rename to packages/webview_flutter/webview_flutter_wkwebview/test/src/common/test_web_kit.g.dart index 73c1053f517d..5c31f63c3add 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/test/src/common/test_web_kit.pigeon.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/test/src/common/test_web_kit.g.dart @@ -11,7 +11,7 @@ import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:webview_flutter_wkwebview/src/common/web_kit.pigeon.dart'; +import 'package:webview_flutter_wkwebview/src/common/web_kit.g.dart'; class _TestWKWebsiteDataStoreHostApiCodec extends StandardMessageCodec { const _TestWKWebsiteDataStoreHostApiCodec(); diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/src/foundation/foundation_test.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/src/foundation/foundation_test.dart index 87b659885b52..b9536208c716 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/test/src/foundation/foundation_test.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/test/src/foundation/foundation_test.dart @@ -8,11 +8,11 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:webview_flutter_wkwebview/src/common/instance_manager.dart'; -import 'package:webview_flutter_wkwebview/src/common/web_kit.pigeon.dart'; +import 'package:webview_flutter_wkwebview/src/common/web_kit.g.dart'; import 'package:webview_flutter_wkwebview/src/foundation/foundation.dart'; import 'package:webview_flutter_wkwebview/src/foundation/foundation_api_impls.dart'; -import '../common/test_web_kit.pigeon.dart'; +import '../common/test_web_kit.g.dart'; import 'foundation_test.mocks.dart'; @GenerateMocks([ diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/src/foundation/foundation_test.mocks.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/src/foundation/foundation_test.mocks.dart index fe80a54ed9ac..d93198ed9d2f 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/test/src/foundation/foundation_test.mocks.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/test/src/foundation/foundation_test.mocks.dart @@ -4,10 +4,9 @@ // ignore_for_file: no_leading_underscores_for_library_prefixes import 'package:mockito/mockito.dart' as _i1; -import 'package:webview_flutter_wkwebview/src/common/web_kit.pigeon.dart' - as _i3; +import 'package:webview_flutter_wkwebview/src/common/web_kit.g.dart' as _i3; -import '../common/test_web_kit.pigeon.dart' as _i2; +import '../common/test_web_kit.g.dart' as _i2; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/src/ui_kit/ui_kit_test.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/src/ui_kit/ui_kit_test.dart index f2250e1ac423..f6295668363f 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/test/src/ui_kit/ui_kit_test.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/test/src/ui_kit/ui_kit_test.dart @@ -12,7 +12,7 @@ import 'package:webview_flutter_wkwebview/src/common/instance_manager.dart'; import 'package:webview_flutter_wkwebview/src/ui_kit/ui_kit.dart'; import 'package:webview_flutter_wkwebview/src/web_kit/web_kit.dart'; -import '../common/test_web_kit.pigeon.dart'; +import '../common/test_web_kit.g.dart'; import 'ui_kit_test.mocks.dart'; @GenerateMocks([ diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/src/ui_kit/ui_kit_test.mocks.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/src/ui_kit/ui_kit_test.mocks.dart index 660c4485ab1b..6200b8dbcadf 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/test/src/ui_kit/ui_kit_test.mocks.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/test/src/ui_kit/ui_kit_test.mocks.dart @@ -6,10 +6,9 @@ import 'dart:async' as _i4; import 'package:mockito/mockito.dart' as _i1; -import 'package:webview_flutter_wkwebview/src/common/web_kit.pigeon.dart' - as _i3; +import 'package:webview_flutter_wkwebview/src/common/web_kit.g.dart' as _i3; -import '../common/test_web_kit.pigeon.dart' as _i2; +import '../common/test_web_kit.g.dart' as _i2; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/src/web_kit/web_kit_test.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/src/web_kit/web_kit_test.dart index 401aebfb8b25..dd007869f0e3 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/test/src/web_kit/web_kit_test.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/test/src/web_kit/web_kit_test.dart @@ -9,12 +9,12 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:webview_flutter_wkwebview/src/common/instance_manager.dart'; -import 'package:webview_flutter_wkwebview/src/common/web_kit.pigeon.dart'; +import 'package:webview_flutter_wkwebview/src/common/web_kit.g.dart'; import 'package:webview_flutter_wkwebview/src/foundation/foundation.dart'; import 'package:webview_flutter_wkwebview/src/web_kit/web_kit.dart'; import 'package:webview_flutter_wkwebview/src/web_kit/web_kit_api_impls.dart'; -import '../common/test_web_kit.pigeon.dart'; +import '../common/test_web_kit.g.dart'; import 'web_kit_test.mocks.dart'; @GenerateMocks([ diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/src/web_kit/web_kit_test.mocks.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/src/web_kit/web_kit_test.mocks.dart index a1a5bf224596..50e09560ed19 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/test/src/web_kit/web_kit_test.mocks.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/test/src/web_kit/web_kit_test.mocks.dart @@ -6,10 +6,9 @@ import 'dart:async' as _i3; import 'package:mockito/mockito.dart' as _i1; -import 'package:webview_flutter_wkwebview/src/common/web_kit.pigeon.dart' - as _i4; +import 'package:webview_flutter_wkwebview/src/common/web_kit.g.dart' as _i4; -import '../common/test_web_kit.pigeon.dart' as _i2; +import '../common/test_web_kit.g.dart' as _i2; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values diff --git a/script/tool/lib/src/common/package_state_utils.dart b/script/tool/lib/src/common/package_state_utils.dart index 464dac6c18d6..fbba75c6116f 100644 --- a/script/tool/lib/src/common/package_state_utils.dart +++ b/script/tool/lib/src/common/package_state_utils.dart @@ -171,6 +171,9 @@ Future _isDevChange(List pathComponents, // The top-level "tool" directory is for non-client-facing utility // code, such as test scripts. pathComponents.first == 'tool' || + // The top-level "pigeons" directory is the repo convention for storing + // pigeon input files. + pathComponents.first == 'pigeons' || // Entry point for the 'custom-test' command, which is only for CI and // local testing. pathComponents.first == 'run_tests.sh' || diff --git a/script/tool/test/common/package_state_utils_test.dart b/script/tool/test/common/package_state_utils_test.dart index 86029cdf73a8..9b6429a084ce 100644 --- a/script/tool/test/common/package_state_utils_test.dart +++ b/script/tool/test/common/package_state_utils_test.dart @@ -68,6 +68,8 @@ void main() { 'packages/a_plugin/example/android/src/androidTest/foo/bar/FooTest.java', 'packages/a_plugin/example/ios/RunnerTests/Foo.m', 'packages/a_plugin/example/ios/RunnerUITests/info.plist', + // Pigeon input. + 'packages/a_plugin/pigeons/messages.dart', // Test scripts. 'packages/a_plugin/run_tests.sh', // Tools. From 8fcff8756c9888ac112afa12baacbb186b07f982 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Tue, 31 Jan 2023 15:56:39 -0500 Subject: [PATCH 057/130] Roll Flutter from 75680ae99e85 to 0a22a1dbf475 (3 revisions) (#7051) * e8c0e467d Roll Flutter Engine from 649362168faa to 153a33dad61b (2 revisions) (flutter/flutter#119433) * 1b2edb8aa dd3e975f3 Roll Fuchsia Mac SDK from 1TFy9RSFMfNy7JpQU... to 9y7C2oamTv6Py4JSC... (flutter/engine#39233) (flutter/flutter#119446) * 0a22a1dbf Roll Flutter Engine from dd3e975f3188 to 49ea2123a1a9 (2 revisions) (flutter/flutter#119454) --- .ci/flutter_master.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/flutter_master.version b/.ci/flutter_master.version index 17714f3e5953..38088c73d50e 100644 --- a/.ci/flutter_master.version +++ b/.ci/flutter_master.version @@ -1 +1 @@ -75680ae99e858a075778571ca01ce1668ba2b819 +0a22a1dbf475e567d97ddae079392adcd6745b8d From 3d81a0071fcd6fe6e01b5ed5f1e8ab2d2620f3b1 Mon Sep 17 00:00:00 2001 From: stuartmorgan Date: Tue, 31 Jan 2023 16:52:49 -0800 Subject: [PATCH 058/130] [tool] More main-branch detection improvement (#7067) Follow-up to https://github.com/flutter/plugins/pull/7038 to try to make it work on LUCI. Looking at the checkout steps of a LUCI run, it looks like we do a full `fetch` of `origin`, but likely only have a `master` branch locally. Rather than rely on a specific local branch name existing, this allows for checking `origin` (and just in case, since it's another common remote name, `upstream`). Hopefully fixes https://github.com/flutter/flutter/issues/119330 --- .../tool/lib/src/common/package_command.dart | 26 ++++++++++-- script/tool/pubspec.yaml | 2 +- .../test/common/package_command_test.dart | 40 +++++++++++++++++++ 3 files changed, 63 insertions(+), 5 deletions(-) diff --git a/script/tool/lib/src/common/package_command.dart b/script/tool/lib/src/common/package_command.dart index 0e084c4c2d5c..8a2bbfc40058 100644 --- a/script/tool/lib/src/common/package_command.dart +++ b/script/tool/lib/src/common/package_command.dart @@ -538,10 +538,28 @@ abstract class PackageCommand extends Command { // This is used because CI may check out a specific hash rather than a branch, // in which case branch-name detection won't work. Future _isCheckoutFromBranch(String branchName) async { - final io.ProcessResult result = await (await gitDir).runCommand( - ['merge-base', '--is-ancestor', 'HEAD', branchName], - throwOnError: false); - return result.exitCode == 0; + // The target branch may not exist locally; try some common remote names for + // the branch as well. + final List candidateBranchNames = [ + branchName, + 'origin/$branchName', + 'upstream/$branchName', + ]; + for (final String branch in candidateBranchNames) { + final io.ProcessResult result = await (await gitDir).runCommand( + ['merge-base', '--is-ancestor', 'HEAD', branch], + throwOnError: false); + if (result.exitCode == 0) { + return true; + } else if (result.exitCode == 1) { + // 1 indicates that the branch was successfully checked, but it's not + // an ancestor. + return false; + } + // Any other return code is an error, such as `branch` not being a valid + // name in the repository, so try other name variants. + } + return false; } Future _getBranch() async { diff --git a/script/tool/pubspec.yaml b/script/tool/pubspec.yaml index 73429638a402..52d23b8f72a3 100644 --- a/script/tool/pubspec.yaml +++ b/script/tool/pubspec.yaml @@ -1,7 +1,7 @@ name: flutter_plugin_tools description: Productivity utils for flutter/plugins and flutter/packages repository: https://github.com/flutter/plugins/tree/main/script/tool -version: 0.13.4+1 +version: 0.13.4+2 dependencies: args: ^2.1.0 diff --git a/script/tool/test/common/package_command_test.dart b/script/tool/test/common/package_command_test.dart index ed76408bb300..3620f8fd63a9 100644 --- a/script/tool/test/common/package_command_test.dart +++ b/script/tool/test/common/package_command_test.dart @@ -875,6 +875,46 @@ packages/b_package/lib/src/foo.dart )); }); + test( + 'only tests changed packages relative to the previous commit if ' + 'running on a specific hash from origin/main', () async { + processRunner.mockProcessesForExecutable['git-diff'] = [ + MockProcess(stdout: 'packages/plugin1/plugin1.dart'), + ]; + processRunner.mockProcessesForExecutable['git-rev-parse'] = [ + MockProcess(stdout: 'HEAD'), + ]; + processRunner.mockProcessesForExecutable['git-merge-base'] = [ + MockProcess(exitCode: 128), // Fail with a non-1 exit code for 'main' + MockProcess(), // Succeed for the variant. + ]; + final RepositoryPackage plugin1 = + createFakePlugin('plugin1', packagesDir); + createFakePlugin('plugin2', packagesDir); + + final List output = await runCapturingPrint( + runner, ['sample', '--packages-for-branch']); + + expect(command.plugins, unorderedEquals([plugin1.path])); + expect( + output, + containsAllInOrder([ + contains( + '--packages-for-branch: running on a commit from default branch.'), + contains( + '--packages-for-branch: using parent commit as the diff base'), + contains( + 'Running for all packages that have diffs relative to "HEAD~"'), + ])); + // Ensure that it's diffing against the prior commit. + expect( + processRunner.recordedCalls, + contains( + const ProcessCall( + 'git-diff', ['--name-only', 'HEAD~', 'HEAD'], null), + )); + }); + test( 'only tests changed packages relative to the previous commit on master', () async { From e41f9f95e02a351b134b5b3f435739ef36d45b5a Mon Sep 17 00:00:00 2001 From: stuartmorgan Date: Tue, 31 Jan 2023 16:53:13 -0800 Subject: [PATCH 059/130] [ci] Clean up analysis options (#7068) Removes some options that are no longer necessary, further aligning the options with flutter/packages. Part of https://github.com/flutter/flutter/issues/113764 --- analysis_options.yaml | 39 +++++---------------------------------- 1 file changed, 5 insertions(+), 34 deletions(-) diff --git a/analysis_options.yaml b/analysis_options.yaml index 3f12e0bf7038..498d19dfb4ae 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,50 +1,21 @@ -# This is a copy (as of March 2021) of flutter/flutter's analysis_options file, -# with minimal changes for this repository. The goal is to move toward using a -# shared set of analysis options as much as possible, and eventually a shared -# file. - # Specify analysis options. # -# 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/main/pkg/analyzer#configuring-the-analyzer -# -# There are other similar analysis options files in the flutter repos, -# which should be kept in sync with this file: -# -# - analysis_options.yaml (this file) -# - packages/flutter/lib/analysis_options_user.yaml -# - https://github.com/flutter/flutter/blob/master/analysis_options.yaml -# - https://github.com/flutter/engine/blob/main/analysis_options.yaml -# - https://github.com/flutter/packages/blob/main/analysis_options.yaml -# -# This file contains the analysis options used for code in the flutter/plugins -# repository. +# This file is a copy of analysis_options.yaml from flutter repo +# as of 2022-07-27, but with some modifications marked with +# "DIFFERENT FROM FLUTTER/FLUTTER" below. The file is expected to +# be kept in sync with the master file from the flutter repo. analyzer: language: strict-casts: true strict-raw-types: true 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 TODO comments in the code - todo: ignore # allow self-reference to deprecated members (we do this because otherwise we have # to annotate every member in every test, assert, etc, when we deprecate something) deprecated_member_use_from_same_package: ignore - # Ignore analyzer hints for updating pubspecs when using Future or - # Stream and not importing dart:async - # Please see https://github.com/flutter/flutter/pull/24528 for details. - sdk_version_async_exported_from_core: ignore # Turned off until null-safe rollout is complete. unnecessary_null_comparison: ignore - ### Local flutter/plugins changes ### - # Allow null checks for as long as mixed mode is officially supported. - always_require_non_null_named_parameters: false # not needed with nnbd - exclude: + exclude: # DIFFERENT FROM FLUTTER/FLUTTER # Ignore generated files - '**/*.g.dart' - '**/*.mocks.dart' # Mockito @GenerateMocks From 4cf6f44947e5cacaac6f890f9a0909c4930c40c0 Mon Sep 17 00:00:00 2001 From: Chris Yang Date: Tue, 31 Jan 2023 16:53:16 -0800 Subject: [PATCH 060/130] [in_app_puchase_storekit] handle `appStoreReceiptURL` is nil (#7069) * handle nil * versio update --- .../in_app_purchase_storekit/CHANGELOG.md | 4 ++++ .../darwin/Classes/FIAPReceiptManager.m | 3 +++ .../RunnerTests/InAppPurchasePluginTests.m | 17 +++++++++++++++++ .../in_app_purchase_storekit/pubspec.yaml | 2 +- 4 files changed, 25 insertions(+), 1 deletion(-) diff --git a/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md b/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md index 5d93cc085843..569e0717fd38 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md +++ b/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.3.5+2 + +* Fix a crash when `appStoreReceiptURL` is nil. + ## 0.3.5+1 * Uses the new `sharedDarwinSource` flag when available. diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPReceiptManager.m b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPReceiptManager.m index fc125da133d4..320e6072d046 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPReceiptManager.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPReceiptManager.m @@ -21,6 +21,9 @@ @implementation FIAPReceiptManager - (NSString *)retrieveReceiptWithError:(FlutterError **)flutterError { NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL]; + if (!receiptURL) { + return nil; + } NSError *receiptError; NSData *receipt = [self getReceiptData:receiptURL error:&receiptError]; if (!receipt || receiptError) { diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.m b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.m index 9ace425ce1dc..f7e6dcdaab16 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.m @@ -313,6 +313,23 @@ - (void)testRetrieveReceiptDataSuccess { XCTAssert([result isKindOfClass:[NSString class]]); } +- (void)testRetrieveReceiptDataNil { + NSBundle *mockBundle = OCMPartialMock([NSBundle mainBundle]); + OCMStub(mockBundle.appStoreReceiptURL).andReturn(nil); + XCTestExpectation *expectation = [self expectationWithDescription:@"nil receipt data retrieved"]; + FlutterMethodCall *call = [FlutterMethodCall + methodCallWithMethodName:@"-[InAppPurchasePlugin retrieveReceiptData:result:]" + arguments:nil]; + __block NSDictionary *result; + [self.plugin handleMethodCall:call + result:^(id r) { + result = r; + [expectation fulfill]; + }]; + [self waitForExpectations:@[ expectation ] timeout:5]; + XCTAssertNil(result); +} + - (void)testRetrieveReceiptDataError { XCTestExpectation *expectation = [self expectationWithDescription:@"receipt data retrieved"]; FlutterMethodCall *call = [FlutterMethodCall diff --git a/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml b/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml index 3a4b0fc04f94..78ae8b16d524 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml +++ b/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml @@ -2,7 +2,7 @@ name: in_app_purchase_storekit description: An implementation for the iOS and macOS platforms of the Flutter `in_app_purchase` plugin. This uses the StoreKit Framework. repository: https://github.com/flutter/plugins/tree/main/packages/in_app_purchase/in_app_purchase_storekit issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+in_app_purchase%22 -version: 0.3.5+1 +version: 0.3.5+2 environment: sdk: ">=2.14.0 <3.0.0" From d5bab1668ef2e11dfc2c80f033fb1b8300b245ae Mon Sep 17 00:00:00 2001 From: Ian Hickson Date: Tue, 31 Jan 2023 17:05:07 -0800 Subject: [PATCH 061/130] prepare for TestDefaultBinaryMessengerBinding.instance becoming non-nullable (#6847) --- .../camera_android/example/lib/main.dart | 1 - .../test/android_camera_test.dart | 26 ++++++++++++------- .../camera_avfoundation/example/lib/main.dart | 1 - .../test/avfoundation_camera_test.dart | 26 ++++++++++++------- .../google_maps_flutter_android_test.dart | 9 ++++++- .../test/google_maps_flutter_ios_test.dart | 9 ++++++- ...thod_channel_google_maps_flutter_test.dart | 9 ++++++- .../url_launcher/lib/src/legacy_api.dart | 1 - .../test/src/legacy_api_test.dart | 2 -- .../lib/link.dart | 1 - .../video_player/lib/video_player.dart | 1 - .../test/android_video_player_test.dart | 1 - .../test/avfoundation_video_player_test.dart | 1 - 13 files changed, 58 insertions(+), 30 deletions(-) diff --git a/packages/camera/camera_android/example/lib/main.dart b/packages/camera/camera_android/example/lib/main.dart index a66d6e168aff..4d98aed9a4c2 100644 --- a/packages/camera/camera_android/example/lib/main.dart +++ b/packages/camera/camera_android/example/lib/main.dart @@ -1091,5 +1091,4 @@ Future main() async { /// /// We use this so that APIs that have become non-nullable can still be used /// with `!` and `?` on the stable branch. -// TODO(ianh): Remove this once we roll stable in late 2021. T? _ambiguate(T? value) => value; diff --git a/packages/camera/camera_android/test/android_camera_test.dart b/packages/camera/camera_android/test/android_camera_test.dart index bd55b0b722ba..d80bd9cac7a3 100644 --- a/packages/camera/camera_android/test/android_camera_test.dart +++ b/packages/camera/camera_android/test/android_camera_test.dart @@ -32,14 +32,15 @@ void main() { // registerWith is called very early in initialization the bindings won't // have been initialized. While registerWith could intialize them, that // could slow down startup, so instead the handler should be set up lazily. - final ByteData? response = await TestDefaultBinaryMessengerBinding - .instance!.defaultBinaryMessenger - .handlePlatformMessage( - AndroidCamera.deviceEventChannelName, - const StandardMethodCodec().encodeMethodCall(const MethodCall( - 'orientation_changed', - {'orientation': 'portraitDown'})), - (ByteData? data) {}); + final ByteData? response = + await _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .handlePlatformMessage( + AndroidCamera.deviceEventChannelName, + const StandardMethodCodec().encodeMethodCall(const MethodCall( + 'orientation_changed', + {'orientation': 'portraitDown'})), + (ByteData? data) {}); expect(response, null); }); @@ -421,7 +422,8 @@ void main() { const DeviceOrientationChangedEvent event = DeviceOrientationChangedEvent(DeviceOrientation.portraitUp); for (int i = 0; i < 3; i++) { - await TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger + await _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger .handlePlatformMessage( AndroidCamera.deviceEventChannelName, const StandardMethodCodec().encodeMethodCall( @@ -1121,3 +1123,9 @@ void main() { }); }); } + +/// This allows a value of type T or T? to be treated as a value of type T?. +/// +/// We use this so that APIs that have become non-nullable can still be used +/// with `!` and `?` on the stable branch. +T? _ambiguate(T? value) => value; diff --git a/packages/camera/camera_avfoundation/example/lib/main.dart b/packages/camera/camera_avfoundation/example/lib/main.dart index a66d6e168aff..4d98aed9a4c2 100644 --- a/packages/camera/camera_avfoundation/example/lib/main.dart +++ b/packages/camera/camera_avfoundation/example/lib/main.dart @@ -1091,5 +1091,4 @@ Future main() async { /// /// We use this so that APIs that have become non-nullable can still be used /// with `!` and `?` on the stable branch. -// TODO(ianh): Remove this once we roll stable in late 2021. T? _ambiguate(T? value) => value; diff --git a/packages/camera/camera_avfoundation/test/avfoundation_camera_test.dart b/packages/camera/camera_avfoundation/test/avfoundation_camera_test.dart index 50d3e9875be1..5d0b74cf0c0c 100644 --- a/packages/camera/camera_avfoundation/test/avfoundation_camera_test.dart +++ b/packages/camera/camera_avfoundation/test/avfoundation_camera_test.dart @@ -32,14 +32,15 @@ void main() { // registerWith is called very early in initialization the bindings won't // have been initialized. While registerWith could intialize them, that // could slow down startup, so instead the handler should be set up lazily. - final ByteData? response = await TestDefaultBinaryMessengerBinding - .instance!.defaultBinaryMessenger - .handlePlatformMessage( - AVFoundationCamera.deviceEventChannelName, - const StandardMethodCodec().encodeMethodCall(const MethodCall( - 'orientation_changed', - {'orientation': 'portraitDown'})), - (ByteData? data) {}); + final ByteData? response = + await _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .handlePlatformMessage( + AVFoundationCamera.deviceEventChannelName, + const StandardMethodCodec().encodeMethodCall(const MethodCall( + 'orientation_changed', + {'orientation': 'portraitDown'})), + (ByteData? data) {}); expect(response, null); }); @@ -421,7 +422,8 @@ void main() { const DeviceOrientationChangedEvent event = DeviceOrientationChangedEvent(DeviceOrientation.portraitUp); for (int i = 0; i < 3; i++) { - await TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger + await _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger .handlePlatformMessage( AVFoundationCamera.deviceEventChannelName, const StandardMethodCodec().encodeMethodCall( @@ -1122,3 +1124,9 @@ void main() { }); }); } + +/// This allows a value of type T or T? to be treated as a value of type T?. +/// +/// We use this so that APIs that have become non-nullable can still be used +/// with `!` and `?` on the stable branch. +T? _ambiguate(T? value) => value; diff --git a/packages/google_maps_flutter/google_maps_flutter_android/test/google_maps_flutter_android_test.dart b/packages/google_maps_flutter/google_maps_flutter_android/test/google_maps_flutter_android_test.dart index 431c2472945e..6f9edad9cb71 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/test/google_maps_flutter_android_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter_android/test/google_maps_flutter_android_test.dart @@ -37,7 +37,8 @@ void main() { int mapId, String method, Map data) async { final ByteData byteData = const StandardMethodCodec().encodeMethodCall(MethodCall(method, data)); - await TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger + await _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger .handlePlatformMessage('plugins.flutter.dev/google_maps_android_$mapId', byteData, (ByteData? data) {}); } @@ -164,3 +165,9 @@ void main() { expect(widget, isA()); }); } + +/// This allows a value of type T or T? to be treated as a value of type T?. +/// +/// We use this so that APIs that have become non-nullable can still be used +/// with `!` and `?` on the stable branch. +T? _ambiguate(T? value) => value; diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/test/google_maps_flutter_ios_test.dart b/packages/google_maps_flutter/google_maps_flutter_ios/test/google_maps_flutter_ios_test.dart index 136481cf3abb..fb23ab24aaeb 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/test/google_maps_flutter_ios_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter_ios/test/google_maps_flutter_ios_test.dart @@ -36,7 +36,8 @@ void main() { int mapId, String method, Map data) async { final ByteData byteData = const StandardMethodCodec().encodeMethodCall(MethodCall(method, data)); - await TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger + await _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger .handlePlatformMessage('plugins.flutter.dev/google_maps_ios_$mapId', byteData, (ByteData? data) {}); } @@ -122,3 +123,9 @@ void main() { equals('drag-end-marker')); }); } + +/// This allows a value of type T or T? to be treated as a value of type T?. +/// +/// We use this so that APIs that have become non-nullable can still be used +/// with `!` and `?` on the stable branch. +T? _ambiguate(T? value) => value; diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/method_channel/method_channel_google_maps_flutter_test.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/method_channel/method_channel_google_maps_flutter_test.dart index e5052184915f..18743dd1f00e 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/method_channel/method_channel_google_maps_flutter_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/method_channel/method_channel_google_maps_flutter_test.dart @@ -36,7 +36,8 @@ void main() { int mapId, String method, Map data) async { final ByteData byteData = const StandardMethodCodec() .encodeMethodCall(MethodCall(method, data)); - await TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger + await _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger .handlePlatformMessage('plugins.flutter.io/google_maps_$mapId', byteData, (ByteData? data) {}); } @@ -120,3 +121,9 @@ void main() { }); }); } + +/// This allows a value of type T or T? to be treated as a value of type T?. +/// +/// We use this so that APIs that have become non-nullable can still be used +/// with `!` and `?` on the stable branch. +T? _ambiguate(T? value) => value; diff --git a/packages/url_launcher/url_launcher/lib/src/legacy_api.dart b/packages/url_launcher/url_launcher/lib/src/legacy_api.dart index 5bd5ef63d5b4..9f6d2dca001e 100644 --- a/packages/url_launcher/url_launcher/lib/src/legacy_api.dart +++ b/packages/url_launcher/url_launcher/lib/src/legacy_api.dart @@ -150,5 +150,4 @@ Future closeWebView() async { /// /// We use this so that APIs that have become non-nullable can still be used /// with `!` and `?` on the stable branch. -// TODO(ianh): Remove this once we roll stable in late 2021. T? _ambiguate(T? value) => value; diff --git a/packages/url_launcher/url_launcher/test/src/legacy_api_test.dart b/packages/url_launcher/url_launcher/test/src/legacy_api_test.dart index 40336a090ab7..b2fde31d526d 100644 --- a/packages/url_launcher/url_launcher/test/src/legacy_api_test.dart +++ b/packages/url_launcher/url_launcher/test/src/legacy_api_test.dart @@ -321,8 +321,6 @@ void main() { /// This removes the type information from a value so that it can be cast /// to another type even if that cast is redundant. -/// /// We use this so that APIs whose type have become more descriptive can still /// be used on the stable branch where they require a cast. -// TODO(ianh): Remove this once we roll stable in late 2021. Object? _anonymize(T? value) => value; diff --git a/packages/url_launcher/url_launcher_platform_interface/lib/link.dart b/packages/url_launcher/url_launcher_platform_interface/lib/link.dart index da8aa1570bad..bddadad893a7 100644 --- a/packages/url_launcher/url_launcher_platform_interface/lib/link.dart +++ b/packages/url_launcher/url_launcher_platform_interface/lib/link.dart @@ -109,5 +109,4 @@ Future pushRouteNameToFramework(Object? _, String routeName) { /// /// We use this so that APIs that have become non-nullable can still be used /// with `!` and `?` on the stable branch. -// TODO(ianh): Remove this once we roll stable in late 2021. T? _ambiguate(T? value) => value; diff --git a/packages/video_player/video_player/lib/video_player.dart b/packages/video_player/video_player/lib/video_player.dart index 3dbdcb543082..5720e2d9d136 100644 --- a/packages/video_player/video_player/lib/video_player.dart +++ b/packages/video_player/video_player/lib/video_player.dart @@ -1100,5 +1100,4 @@ class ClosedCaption extends StatelessWidget { /// /// We use this so that APIs that have become non-nullable can still be used /// with `!` and `?` on the stable branch. -// TODO(ianh): Remove this once we roll stable in late 2021. T? _ambiguate(T? value) => value; diff --git a/packages/video_player/video_player_android/test/android_video_player_test.dart b/packages/video_player/video_player_android/test/android_video_player_test.dart index bca0d9d68e65..fa7ca7aa7f7a 100644 --- a/packages/video_player/video_player_android/test/android_video_player_test.dart +++ b/packages/video_player/video_player_android/test/android_video_player_test.dart @@ -360,5 +360,4 @@ void main() { /// /// We use this so that APIs that have become non-nullable can still be used /// with `!` and `?` on the stable branch. -// TODO(ianh): Remove this once we roll stable in late 2021. T? _ambiguate(T? value) => value; diff --git a/packages/video_player/video_player_avfoundation/test/avfoundation_video_player_test.dart b/packages/video_player/video_player_avfoundation/test/avfoundation_video_player_test.dart index 3367c9fc9ba6..e7c3b5ba4ff3 100644 --- a/packages/video_player/video_player_avfoundation/test/avfoundation_video_player_test.dart +++ b/packages/video_player/video_player_avfoundation/test/avfoundation_video_player_test.dart @@ -339,5 +339,4 @@ void main() { /// /// We use this so that APIs that have become non-nullable can still be used /// with `!` and `?` on the stable branch. -// TODO(ianh): Remove this once we roll stable in late 2021. T? _ambiguate(T? value) => value; From 36d8066cf7b57478dafa7b838b3d463152428e82 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Wed, 1 Feb 2023 12:06:42 -0500 Subject: [PATCH 062/130] Roll Flutter from 0a22a1dbf475 to d27880801435 (58 revisions) (#7078) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 472b887d5 0ec8e2802 Roll Fuchsia Mac SDK from 9y7C2oamTv6Py4JSC... to EAFnGijD0l5QxaPxF... (flutter/engine#39236) (flutter/flutter#119461) * 15cd00f1e a7bb0e410 Roll Fuchsia Linux SDK from 1D63BqURfJdG4r3CK... to xTXbcsPr5GJvFSLha... (flutter/engine#39238) (flutter/flutter#119482) * 530c3f2d1 [Re-land#2] Button padding M3 (flutter/flutter#119498) * 17eb2e8ae Ability to disable the browser's context menu on web (flutter/flutter#118194) * df8ad3d2c roll packages (flutter/flutter#119370) * b68cebd9c roll packages (flutter/flutter#119530) * 59d80dc87 [Android] Add explicit exported tag to Linux_android flavors test (flutter/flutter#117542) * 458b298f9 Refactoring to use `ver` command instead of `systeminfo` (flutter/flutter#119304) * 54405bfa3 fixes PointerEventConverter to handle malformed scrolling event (flutter/flutter#118124) * e69ea6dee Support flipping mouse scrolling axes through modifier keys (flutter/flutter#115610) * 92df6b4bc 396c7fd0b Reland "Remove references to Observatory (#38919)" (flutter/engine#39139) (flutter/flutter#119546) * 7477d7ac7 Reland "Add --serve-observatory flag to run, attach, and test (#118402)" (flutter/flutter#119529) * 6c12e3994 Introduce ParagraphBoundary subclass for text editing (flutter/flutter#116549) * b227df308 Hint text semantics to be excluded in a11y read out if hintText is not visible. (flutter/flutter#119198) * 18c7f8a27 Fix typo in --machine help text (flutter/flutter#119563) * 329f86a90 Make a few values non-nullable in cupertino (flutter/flutter#119478) * c4520bc8b b2efe0175 [web] Expose felt flag for building CanvasKit Chromium (flutter/engine#39201) (flutter/flutter#119567) * 8898f4f19 Marks Mac_android run_debug_test_android to be unflaky (flutter/flutter#117468) * 1f0b6fbd7 Remove deprecated AppBar/SliverAppBar/AppBarTheme.textTheme member (flutter/flutter#119253) * edaeec8ed Roll Flutter Engine from b2efe01754ef to 5011144c0b46 (3 revisions) (flutter/flutter#119578) * 865dc5c51 Roll Flutter Engine from 5011144c0b46 to daa8eeb7fc0b (2 revisions) (flutter/flutter#119584) * 1148a2a8b Migrate EditableTextState from addPostFrameCallbacks to compositionCallbacks (flutter/flutter#119359) * 234090253 Roll Flutter Engine from daa8eeb7fc0b to 77218818138f (3 revisions) (flutter/flutter#119586) * 65900b71b Remove deprecated AnimatedSize.vsync parameter (flutter/flutter#119186) * 5b6572f96 Add debug diagnostics to channels integration test (flutter/flutter#119579) * 504e5652f Roll Flutter Engine from 77218818138f to 9448f2966c11 (3 revisions) (flutter/flutter#119592) * 7ba440655 Revert "[Re-land#2] Button padding M3 (#119498)" (flutter/flutter#119597) * 2c34a88eb Roll Flutter Engine from 9448f2966c11 to 72abe0e4b828 (3 revisions) (flutter/flutter#119603) * df0ab40ec Roll Plugins from ff84c44a5ddb to 9da327ca39c4 (15 revisions) (flutter/flutter#119629) * 67d07a6de [flutter_tools] Fix parsing of existing DDS URIs from exceptions (flutter/flutter#119506) * d272a3ab8 Reland: [macos] add flavor options to tool commands (flutter/flutter#119564) * a16d82cba aa00da3c1 Roll Skia from fc31f43cc40a to 3c6eb76a683a (1 revision) (flutter/engine#39280) (flutter/flutter#119605) * f6b0c6dde Use first Dart VM Service found with mDNS if there are duplicates (flutter/flutter#119545) * d4c74858f Make Decoration.padding non-nullable (flutter/flutter#119581) * 2fccf4d47 Remove MediaQuery from WidgetsApp (flutter/flutter#119377) * 9b3b9cf08 Roll Flutter Engine from aa00da3c1612 to cd2e8885e491 (6 revisions) (flutter/flutter#119639) * 6a5405925 Make MultiChildRenderObjectWidget const (flutter/flutter#119195) * e2b3d89e7 Fix CupertinoNavigationBar should create a backward compatible Annota… (flutter/flutter#119515) * 7bf95f41e 1aaf3db31 Roll Dart SDK from 4fdbc7c28141 to 9bcc1773ebf0 (1 revision) (flutter/engine#39290) (flutter/flutter#119640) * 0e22aca78 Add support for image insertion on Android (flutter/flutter#110052) * ff22813b7 separatorBuilder can't return null (flutter/flutter#119566) * 60c1f293d 2471f430f Update buildroot to c02da5072d1bb2. (flutter/engine#39292) (flutter/flutter#119645) * fbe9ff33e Disable an inaccurate test assertion that will be fixed by an engine roll (flutter/flutter#119653) * 8f90e2a7d Roll Flutter Engine from 2471f430ff4b to bb7b7006f4a3 (2 revisions) (flutter/flutter#119655) * 388438141 Make gen-l10n error handling independent of logger state (flutter/flutter#119644) * 198a51ace Migrate the Material Date pickers to M3 Reprise (flutter/flutter#119033) * dc8656594 Roll Flutter Engine from bb7b7006f4a3 to 521b975449ba (4 revisions) (flutter/flutter#119670) * 82df23539 Undo making Flex,Row,Column const (flutter/flutter#119669) * 6f9a896d7 Roll Flutter Engine from 521b975449ba to 38913c5484cf (2 revisions) (flutter/flutter#119675) * 8d0af3679 🥅 Produce warning instead of error for storage base url overrides (flutter/flutter#119595) * 3894d2481 1703a3966 Roll Skia from c29211525dac to 654f4805e8b8 (21 revisions) (flutter/engine#39309) (flutter/flutter#119683) * a752c2f15 Expose enableIMEPersonalizedLearning on CupertinoSearchTextField (flutter/flutter#119439) * e1f0b1d14 d92e23cb5 Roll Skia from 654f4805e8b8 to da41cf18f651 (1 revision) (flutter/engine#39311) (flutter/flutter#119686) * 97d273ce1 CupertinoThemeData equality (flutter/flutter#119480) * 416783503 5b549950f Roll Fuchsia Linux SDK from 71lEeibIyrq0V8jId... to TFcelQ5SwrzkcYK2d... (flutter/engine#39312) (flutter/flutter#119688) * b4a6e349a 0d87b1562 Roll Dart SDK from 8b57d23a7246 to de03e1f41b50 (1 revision) (flutter/engine#39313) (flutter/flutter#119695) * 3af30ff59 Roll Flutter Engine from 0d87b156265c to c08a286d60e9 (3 revisions) (flutter/flutter#119706) * d27880801 [Re-land] Exposed tooltip longPress (flutter/flutter#118796) --- .ci/flutter_master.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/flutter_master.version b/.ci/flutter_master.version index 38088c73d50e..c91cdff3cd1e 100644 --- a/.ci/flutter_master.version +++ b/.ci/flutter_master.version @@ -1 +1 @@ -0a22a1dbf475e567d97ddae079392adcd6745b8d +d27880801435109432dcada4a2245193af4ae1f2 From ec463866fbcdb98aa568a710551f1391543459c7 Mon Sep 17 00:00:00 2001 From: Michael Goderbauer Date: Wed, 1 Feb 2023 11:02:54 -0800 Subject: [PATCH 063/130] [various] prepare for more const widgets (#7074) --- .../google_maps_flutter/test/map_creation_test.dart | 4 ++++ packages/local_auth/local_auth/example/lib/main.dart | 4 ++++ .../local_auth/local_auth_android/example/lib/main.dart | 4 ++++ packages/local_auth/local_auth_ios/example/lib/main.dart | 4 ++++ .../local_auth/local_auth_windows/example/lib/main.dart | 4 ++++ .../url_launcher/url_launcher/example/lib/encoding.dart | 6 ++++++ 6 files changed, 26 insertions(+) diff --git a/packages/google_maps_flutter/google_maps_flutter/test/map_creation_test.dart b/packages/google_maps_flutter/google_maps_flutter/test/map_creation_test.dart index b34fccbfa422..49b64b1b4b2a 100644 --- a/packages/google_maps_flutter/google_maps_flutter/test/map_creation_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter/test/map_creation_test.dart @@ -29,8 +29,12 @@ void main() { ) async { // Inject two map widgets... await tester.pumpWidget( + // TODO(goderbauer): Make this const when this package requires Flutter 3.8 or later. + // ignore: prefer_const_constructors Directionality( textDirection: TextDirection.ltr, + // TODO(goderbauer): Make this const when this package requires Flutter 3.8 or later. + // ignore: prefer_const_constructors child: Column( children: const [ GoogleMap( diff --git a/packages/local_auth/local_auth/example/lib/main.dart b/packages/local_auth/local_auth/example/lib/main.dart index f2cded002084..146a5d92b29c 100644 --- a/packages/local_auth/local_auth/example/lib/main.dart +++ b/packages/local_auth/local_auth/example/lib/main.dart @@ -183,6 +183,8 @@ class _MyAppState extends State { if (_isAuthenticating) ElevatedButton( onPressed: _cancelAuthentication, + // TODO(goderbauer): Make this const when this package requires Flutter 3.8 or later. + // ignore: prefer_const_constructors child: Row( mainAxisSize: MainAxisSize.min, children: const [ @@ -196,6 +198,8 @@ class _MyAppState extends State { children: [ ElevatedButton( onPressed: _authenticate, + // TODO(goderbauer): Make this const when this package requires Flutter 3.8 or later. + // ignore: prefer_const_constructors child: Row( mainAxisSize: MainAxisSize.min, children: const [ diff --git a/packages/local_auth/local_auth_android/example/lib/main.dart b/packages/local_auth/local_auth_android/example/lib/main.dart index f7d908c81973..f245af973981 100644 --- a/packages/local_auth/local_auth_android/example/lib/main.dart +++ b/packages/local_auth/local_auth_android/example/lib/main.dart @@ -188,6 +188,8 @@ class _MyAppState extends State { if (_isAuthenticating) ElevatedButton( onPressed: _cancelAuthentication, + // TODO(goderbauer): Make this const when this package requires Flutter 3.8 or later. + // ignore: prefer_const_constructors child: Row( mainAxisSize: MainAxisSize.min, children: const [ @@ -201,6 +203,8 @@ class _MyAppState extends State { children: [ ElevatedButton( onPressed: _authenticate, + // TODO(goderbauer): Make this const when this package requires Flutter 3.8 or later. + // ignore: prefer_const_constructors child: Row( mainAxisSize: MainAxisSize.min, children: const [ diff --git a/packages/local_auth/local_auth_ios/example/lib/main.dart b/packages/local_auth/local_auth_ios/example/lib/main.dart index 3aa8d6625232..63b317e54c7b 100644 --- a/packages/local_auth/local_auth_ios/example/lib/main.dart +++ b/packages/local_auth/local_auth_ios/example/lib/main.dart @@ -187,6 +187,8 @@ class _MyAppState extends State { if (_isAuthenticating) ElevatedButton( onPressed: _cancelAuthentication, + // TODO(goderbauer): Make this const when this package requires Flutter 3.8 or later. + // ignore: prefer_const_constructors child: Row( mainAxisSize: MainAxisSize.min, children: const [ @@ -200,6 +202,8 @@ class _MyAppState extends State { children: [ ElevatedButton( onPressed: _authenticate, + // TODO(goderbauer): Make this const when this package requires Flutter 3.8 or later. + // ignore: prefer_const_constructors child: Row( mainAxisSize: MainAxisSize.min, children: const [ diff --git a/packages/local_auth/local_auth_windows/example/lib/main.dart b/packages/local_auth/local_auth_windows/example/lib/main.dart index b173e5414396..3205cdb81bc8 100644 --- a/packages/local_auth/local_auth_windows/example/lib/main.dart +++ b/packages/local_auth/local_auth_windows/example/lib/main.dart @@ -150,6 +150,8 @@ class _MyAppState extends State { if (_isAuthenticating) ElevatedButton( onPressed: _cancelAuthentication, + // TODO(goderbauer): Make this const when this package requires Flutter 3.8 or later. + // ignore: prefer_const_constructors child: Row( mainAxisSize: MainAxisSize.min, children: const [ @@ -163,6 +165,8 @@ class _MyAppState extends State { children: [ ElevatedButton( onPressed: _authenticate, + // TODO(goderbauer): Make this const when this package requires Flutter 3.8 or later. + // ignore: prefer_const_constructors child: Row( mainAxisSize: MainAxisSize.min, children: const [ diff --git a/packages/url_launcher/url_launcher/example/lib/encoding.dart b/packages/url_launcher/url_launcher/example/lib/encoding.dart index 24c724466a77..575eb5f42387 100644 --- a/packages/url_launcher/url_launcher/example/lib/encoding.dart +++ b/packages/url_launcher/url_launcher/example/lib/encoding.dart @@ -22,8 +22,14 @@ String? encodeQueryParameters(Map params) { // #enddocregion encode-query-parameters void main() => runApp( + // TODO(goderbauer): Make this const when this package requires Flutter 3.8 or later. + // ignore: prefer_const_constructors MaterialApp( + // TODO(goderbauer): Make this const when this package requires Flutter 3.8 or later. + // ignore: prefer_const_constructors home: Material( + // TODO(goderbauer): Make this const when this package requires Flutter 3.8 or later. + // ignore: prefer_const_constructors child: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: const [ From dbf8d8d0734287c99b57f52e5fbb897ccd60f1a1 Mon Sep 17 00:00:00 2001 From: Jenn Magder Date: Wed, 1 Feb 2023 13:24:18 -0800 Subject: [PATCH 064/130] Change google_sign_in_ios and image_picker_ios owners (#7070) --- CODEOWNERS | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 8a46d52a07d9..f4d6ede3fc43 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -45,11 +45,11 @@ packages/video_player/video_player_android/** @camsim99 packages/camera/camera_avfoundation/** @hellohuanlin packages/file_selector/file_selector_ios/** @jmagman packages/google_maps_flutter/google_maps_flutter_ios/** @cyanglaz -packages/google_sign_in/google_sign_in_ios/** @jmagman +packages/google_sign_in/google_sign_in_ios/** @vashworth packages/image_picker/image_picker_ios/** @vashworth packages/in_app_purchase/in_app_purchase_storekit/** @cyanglaz packages/ios_platform_images/ios/** @jmagman -packages/local_auth/local_auth_ios/** @hellohuanlin +packages/local_auth/local_auth_ios/** @louisehsu packages/path_provider/path_provider_foundation/** @jmagman packages/quick_actions/quick_actions_ios/** @hellohuanlin packages/shared_preferences/shared_preferences_foundation/** @cyanglaz From 1d4570bb069b5e7dd84b275a6b8618fbdfa440b4 Mon Sep 17 00:00:00 2001 From: Drew Roen <102626803+drewroengoogle@users.noreply.github.com> Date: Thu, 2 Feb 2023 05:31:05 -0600 Subject: [PATCH 065/130] Update README.md (#7076) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 92098af809e9..e7dcaf9be610 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Build Status](https://api.cirrus-ci.com/github/flutter/plugins.svg)](https://cirrus-ci.com/github/flutter/plugins/main) [![Release Status](https://github.com/flutter/plugins/actions/workflows/release.yml/badge.svg)](https://github.com/flutter/plugins/actions/workflows/release.yml) -[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/flutter/plugins/badge)](https://api.securityscorecards.dev/projects/github.com/flutter/plugins) +[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/flutter/plugins/badge)](https://deps.dev/project/github/flutter%2Fplugins) This repo is a companion repo to the main [flutter repo](https://github.com/flutter/flutter). It contains the source code for From 9302d87ee5452344878eed7beb1835c959ab1d27 Mon Sep 17 00:00:00 2001 From: stuartmorgan Date: Thu, 2 Feb 2023 04:32:49 -0800 Subject: [PATCH 066/130] [ci] More cirrus.yml pre-alignment with flutter/packages (#7079) * Rename CI steps/templates to match the more generic naming in flutter/packages * Move Dart unit tests to heavy workload, matching flutter/packages * Tweak outdated comment * Update LUCI script reference to moved file --- .ci/scripts/create_all_plugins_app.sh | 2 +- .cirrus.yml | 41 ++++++++++--------- ...app.yaml => exclude_all_packages_app.yaml} | 0 3 files changed, 23 insertions(+), 20 deletions(-) rename script/configs/{exclude_all_plugins_app.yaml => exclude_all_packages_app.yaml} (100%) diff --git a/.ci/scripts/create_all_plugins_app.sh b/.ci/scripts/create_all_plugins_app.sh index 100e8aca804a..8399e5e38a35 100644 --- a/.ci/scripts/create_all_plugins_app.sh +++ b/.ci/scripts/create_all_plugins_app.sh @@ -4,4 +4,4 @@ # found in the LICENSE file. dart ./script/tool/bin/flutter_plugin_tools.dart create-all-packages-app \ - --output-dir=. --exclude script/configs/exclude_all_plugins_app.yaml + --output-dir=. --exclude script/configs/exclude_all_packages_app.yaml diff --git a/.cirrus.yml b/.cirrus.yml index 66a471020189..8d659c3d72aa 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -47,17 +47,19 @@ flutter_upgrade_template: &FLUTTER_UPGRADE_TEMPLATE - flutter doctor -v << : *TOOL_SETUP_TEMPLATE -build_all_plugins_app_template: &BUILD_ALL_PLUGINS_APP_TEMPLATE - create_all_plugins_app_script: - - $PLUGIN_TOOL_COMMAND create-all-packages-app --output-dir=. --exclude script/configs/exclude_all_plugins_app.yaml - build_all_plugins_debug_script: +# Ensures that the latest versions of all of the 1P packages can be used +# together. See script/configs/exclude_all_packages_app.yaml for exceptions. +build_all_packages_app_template: &BUILD_ALL_PACKAGES_APP_TEMPLATE + create_all_packages_app_script: + - $PLUGIN_TOOL_COMMAND create-all-packages-app --output-dir=. --exclude script/configs/exclude_all_packages_app.yaml + build_all_packages_debug_script: - cd all_packages - if [[ "$BUILD_ALL_ARGS" == "web" ]]; then - echo "Skipping; web does not support debug builds" - else - flutter build $BUILD_ALL_ARGS --debug - fi - build_all_plugins_release_script: + build_all_packages_release_script: - cd all_packages - flutter build $BUILD_ALL_ARGS --release @@ -117,13 +119,6 @@ task: - else - echo "Only run in presubmit" - fi - - name: dart_unit_tests - env: - matrix: - CHANNEL: "master" - CHANNEL: "stable" - unit_test_script: - - ./script/tool_runner.sh test - name: analyze env: matrix: @@ -186,21 +181,21 @@ task: CIRRUS_CLONE_SUBMODULES: true script: ./script/tool_runner.sh update-excerpts --fail-on-change ### Web tasks ### - - name: web-build_all_plugins + - name: web-build_all_packages env: BUILD_ALL_ARGS: "web" matrix: CHANNEL: "master" CHANNEL: "stable" - << : *BUILD_ALL_PLUGINS_APP_TEMPLATE + << : *BUILD_ALL_PACKAGES_APP_TEMPLATE ### Linux desktop tasks ### - - name: linux-build_all_plugins + - name: linux-build_all_packages env: BUILD_ALL_ARGS: "linux" matrix: CHANNEL: "master" CHANNEL: "stable" - << : *BUILD_ALL_PLUGINS_APP_TEMPLATE + << : *BUILD_ALL_PACKAGES_APP_TEMPLATE - name: linux-platform_tests # Don't run full platform tests on both channels in pre-submit. skip: $CIRRUS_PR != '' && $CHANNEL == 'stable' @@ -230,6 +225,14 @@ task: cpu: 4 memory: 16G matrix: + ### Platform-agnostic tasks ### + - name: dart_unit_tests + env: + matrix: + CHANNEL: "master" + CHANNEL: "stable" + unit_test_script: + - ./script/tool_runner.sh test ### Android tasks ### - name: android-platform_tests # Don't run full platform tests on both channels in pre-submit. @@ -251,7 +254,7 @@ task: lint_script: - ./script/tool_runner.sh lint-android # must come after build-examples native_unit_test_script: - # Native integration tests are handled by firebase-test-lab below, so + # Native integration tests are handled by Firebase Test Lab below, so # only run unit tests. # Must come after build-examples. - ./script/tool_runner.sh native-test --android --no-integration --exclude script/configs/exclude_native_unit_android.yaml @@ -268,13 +271,13 @@ task: path: "**/reports/lint-results-debug.xml" type: text/xml format: android-lint - - name: android-build_all_plugins + - name: android-build_all_packages env: BUILD_ALL_ARGS: "apk" matrix: CHANNEL: "master" CHANNEL: "stable" - << : *BUILD_ALL_PLUGINS_APP_TEMPLATE + << : *BUILD_ALL_PACKAGES_APP_TEMPLATE ### Web tasks ### - name: web-platform_tests env: diff --git a/script/configs/exclude_all_plugins_app.yaml b/script/configs/exclude_all_packages_app.yaml similarity index 100% rename from script/configs/exclude_all_plugins_app.yaml rename to script/configs/exclude_all_packages_app.yaml From 48ddee756e90357c5c67b0db019f910a34b83603 Mon Sep 17 00:00:00 2001 From: Jakub Walusiak Date: Fri, 3 Feb 2023 12:30:14 +0100 Subject: [PATCH 067/130] [image_picker_android] Fix analyzer warnings, remove unnecessary changes. --- .../billing_client_manager.dart | 4 ++-- .../sku_details_wrapper.dart | 3 ++- .../src/in_app_purchase_android_platform.dart | 17 ++++++++--------- ..._app_purchase_android_platform_addition.dart | 3 +-- .../billing_client_manager_test.dart | 4 ++++ .../in_app_purchase_android_platform_test.dart | 2 ++ 6 files changed, 19 insertions(+), 14 deletions(-) diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_manager.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_manager.dart index 4427032ef910..31598621da05 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_manager.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_manager.dart @@ -79,7 +79,7 @@ class BillingClientManager { if (result.responseCode == BillingResponse.serviceDisconnected && !_isDisposed) { await _connect(); - return await run(block); + return run(block); } else { return result; } @@ -97,7 +97,7 @@ class BillingClientManager { Future runRaw(Future Function(BillingClient client) block) async { assert(_debugAssertNotDisposed()); await _readyFuture; - return await block(client); + return block(client); } /// Ends connection to the [BillingClient]. diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/sku_details_wrapper.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/sku_details_wrapper.dart index 5ef13c2e2de2..2689cf37eac4 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/sku_details_wrapper.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/sku_details_wrapper.dart @@ -5,7 +5,8 @@ import 'package:flutter/foundation.dart'; import 'package:json_annotation/json_annotation.dart'; -import '../../billing_client_wrappers.dart'; +import 'billing_client_manager.dart'; +import 'billing_client_wrapper.dart'; // WARNING: Changes to `@JsonSerializable` classes need to be reflected in the // below generated file. Run `flutter packages pub run build_runner watch` to diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform.dart index 6349842d5f66..0dea21a579d6 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform.dart @@ -62,14 +62,13 @@ class InAppPurchaseAndroidPlatform extends InAppPurchasePlatform { @override Future isAvailable() async { - return await billingClientManager + return billingClientManager .runRaw((BillingClient client) => client.isReady()); } @override Future queryProductDetails( - Set identifiers, - ) async { + Set identifiers) async { List responses; PlatformException? exception; @@ -156,8 +155,7 @@ class InAppPurchaseAndroidPlatform extends InAppPurchasePlatform { @override Future completePurchase( - PurchaseDetails purchase, - ) async { + PurchaseDetails purchase) async { assert( purchase is GooglePlayPurchaseDetails, 'On Android, the `purchase` should always be of type `GooglePlayPurchaseDetails`.', @@ -175,14 +173,16 @@ class InAppPurchaseAndroidPlatform extends InAppPurchasePlatform { 'completePurchase unsuccessful. The `purchase.verificationData` is not valid'); } - return await billingClientManager.run( + return billingClientManager.run( (BillingClient client) => client.acknowledgePurchase( purchase.verificationData.serverVerificationData), ); } @override - Future restorePurchases({String? applicationUserName}) async { + Future restorePurchases({ + String? applicationUserName, + }) async { List responses; responses = await Future.wait(>[ @@ -253,8 +253,7 @@ class InAppPurchaseAndroidPlatform extends InAppPurchasePlatform { } Future> _getPurchaseDetailsFromResult( - PurchasesResultWrapper resultWrapper, - ) async { + PurchasesResultWrapper resultWrapper) async { IAPError? error; if (resultWrapper.responseCode != BillingResponse.ok) { error = IAPError( diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform_addition.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform_addition.dart index 00deae440cf4..2e117680f3df 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform_addition.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform_addition.dart @@ -159,8 +159,7 @@ class InAppPurchaseAndroidPlatformAddition /// The skuDetails needs to have already been fetched in a /// [InAppPurchaseAndroidPlatform.queryProductDetails] call. Future launchPriceChangeConfirmationFlow({ - required String sku, - }) { + required String sku}) { return _billingClientManager.run( (BillingClient client) => client.launchPriceChangeConfirmationFlow(sku: sku), diff --git a/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/billing_client_manager_test.dart b/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/billing_client_manager_test.dart index b329dd8d51f4..8351395cb46b 100644 --- a/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/billing_client_manager_test.dart +++ b/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/billing_client_manager_test.dart @@ -49,6 +49,7 @@ void main() { test('connects on initialization', () { expect(stubPlatform.countPreviousCalls(startConnectionCall), equals(1)); }); + test('waits for connection before executing the operations', () { bool runCalled = false; bool runRawCalled = false; @@ -63,6 +64,7 @@ void main() { expect(runCalled, equals(true)); expect(runRawCalled, equals(true)); }); + test('re-connects when client sends onBillingServiceDisconnected', () { connectedCompleter.complete(); manager.client.callHandler( @@ -71,6 +73,7 @@ void main() { ); expect(stubPlatform.countPreviousCalls(startConnectionCall), equals(2)); }); + test( 're-connects when operation returns BillingResponse.serviceDisconnected', () async { @@ -91,6 +94,7 @@ void main() { expect(result.responseCode, equals(BillingResponse.ok)); }, ); + test('does not re-connect when disposed', () { connectedCompleter.complete(); manager.dispose(); diff --git a/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_test.dart b/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_test.dart index 397e7e134ce2..351b3bb1f575 100644 --- a/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_test.dart +++ b/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_test.dart @@ -59,6 +59,7 @@ void main() { //await iapAndroidPlatform.isAvailable(); expect(stubPlatform.countPreviousCalls(startConnectionCall), equals(1)); }); + test('re-connects when client sends onBillingServiceDisconnected', () { iapAndroidPlatform.billingClientManager.client.callHandler( const MethodCall(onBillingServiceDisconnectedCallback, @@ -66,6 +67,7 @@ void main() { ); expect(stubPlatform.countPreviousCalls(startConnectionCall), equals(2)); }); + test( 're-connects when operation returns BillingResponse.clientDisconnected', () async { From 97a05241e4c2dbeaf8bac48573cced38c1507940 Mon Sep 17 00:00:00 2001 From: Camille Simon <43054281+camsim99@users.noreply.github.com> Date: Fri, 3 Feb 2023 11:39:24 -0800 Subject: [PATCH 068/130] [camera_android] Default to legacy recording profile when EncoderProfiles unavailable (#7073) * Make changes, start test * Bump versions * Add test * Formatting * Add issue * Fix test * Address review --- packages/camera/camera_android/CHANGELOG.md | 4 + .../io/flutter/plugins/camera/Camera.java | 7 +- .../resolution/ResolutionFeature.java | 33 +++++-- .../camera/media/MediaRecorderBuilder.java | 2 +- .../resolution/ResolutionFeatureTest.java | 98 +++++++++++++++++++ packages/camera/camera_android/pubspec.yaml | 2 +- 6 files changed, 132 insertions(+), 14 deletions(-) diff --git a/packages/camera/camera_android/CHANGELOG.md b/packages/camera/camera_android/CHANGELOG.md index 0cb9957d029d..4609b402058a 100644 --- a/packages/camera/camera_android/CHANGELOG.md +++ b/packages/camera/camera_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.10.4 + +* Temporarily fixes issue with requested video profiles being null by falling back to deprecated behavior in that case. + ## 0.10.3 * Adds back use of Optional type. diff --git a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/Camera.java b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/Camera.java index 7c592b9c7e99..b02d6864b5b7 100644 --- a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/Camera.java +++ b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/Camera.java @@ -258,8 +258,11 @@ private void prepareMediaRecorder(String outputFilePath) throws IOException { MediaRecorderBuilder mediaRecorderBuilder; - if (Build.VERSION.SDK_INT >= 31) { - mediaRecorderBuilder = new MediaRecorderBuilder(getRecordingProfile(), outputFilePath); + // TODO(camsim99): Revert changes that allow legacy code to be used when recordingProfile is null + // once this has largely been fixed on the Android side. https://github.com/flutter/flutter/issues/119668 + EncoderProfiles recordingProfile = getRecordingProfile(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && recordingProfile != null) { + mediaRecorderBuilder = new MediaRecorderBuilder(recordingProfile, outputFilePath); } else { mediaRecorderBuilder = new MediaRecorderBuilder(getRecordingProfileLegacy(), outputFilePath); } diff --git a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/resolution/ResolutionFeature.java b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/resolution/ResolutionFeature.java index afbd7c3758a6..0ec2fbef87de 100644 --- a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/resolution/ResolutionFeature.java +++ b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/resolution/ResolutionFeature.java @@ -114,19 +114,23 @@ static Size computeBestPreviewSize(int cameraId, ResolutionPreset preset) if (preset.ordinal() > ResolutionPreset.high.ordinal()) { preset = ResolutionPreset.high; } - if (Build.VERSION.SDK_INT >= 31) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { EncoderProfiles profile = getBestAvailableCamcorderProfileForResolutionPreset(cameraId, preset); List videoProfiles = profile.getVideoProfiles(); EncoderProfiles.VideoProfile defaultVideoProfile = videoProfiles.get(0); - return new Size(defaultVideoProfile.getWidth(), defaultVideoProfile.getHeight()); - } else { - @SuppressWarnings("deprecation") - CamcorderProfile profile = - getBestAvailableCamcorderProfileForResolutionPresetLegacy(cameraId, preset); - return new Size(profile.videoFrameWidth, profile.videoFrameHeight); + if (defaultVideoProfile != null) { + return new Size(defaultVideoProfile.getWidth(), defaultVideoProfile.getHeight()); + } } + + @SuppressWarnings("deprecation") + // TODO(camsim99): Suppression is currently safe because legacy code is used as a fallback for SDK >= S. + // This should be removed when reverting that fallback behavior: https://github.com/flutter/flutter/issues/119668. + CamcorderProfile profile = + getBestAvailableCamcorderProfileForResolutionPresetLegacy(cameraId, preset); + return new Size(profile.videoFrameWidth, profile.videoFrameHeight); } /** @@ -234,15 +238,24 @@ private void configureResolution(ResolutionPreset resolutionPreset, int cameraId if (!checkIsSupported()) { return; } + boolean captureSizeCalculated = false; - if (Build.VERSION.SDK_INT >= 31) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + recordingProfileLegacy = null; recordingProfile = getBestAvailableCamcorderProfileForResolutionPreset(cameraId, resolutionPreset); List videoProfiles = recordingProfile.getVideoProfiles(); EncoderProfiles.VideoProfile defaultVideoProfile = videoProfiles.get(0); - captureSize = new Size(defaultVideoProfile.getWidth(), defaultVideoProfile.getHeight()); - } else { + + if (defaultVideoProfile != null) { + captureSizeCalculated = true; + captureSize = new Size(defaultVideoProfile.getWidth(), defaultVideoProfile.getHeight()); + } + } + + if (!captureSizeCalculated) { + recordingProfile = null; @SuppressWarnings("deprecation") CamcorderProfile camcorderProfile = getBestAvailableCamcorderProfileForResolutionPresetLegacy(cameraId, resolutionPreset); diff --git a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/media/MediaRecorderBuilder.java b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/media/MediaRecorderBuilder.java index 0aebfee39e0a..1f9f6200bb99 100644 --- a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/media/MediaRecorderBuilder.java +++ b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/media/MediaRecorderBuilder.java @@ -75,7 +75,7 @@ public MediaRecorder build() throws IOException, NullPointerException, IndexOutO if (enableAudio) mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE); - if (Build.VERSION.SDK_INT >= 31) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && encoderProfiles != null) { EncoderProfiles.VideoProfile videoProfile = encoderProfiles.getVideoProfiles().get(0); EncoderProfiles.AudioProfile audioProfile = encoderProfiles.getAudioProfiles().get(0); diff --git a/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/resolution/ResolutionFeatureTest.java b/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/resolution/ResolutionFeatureTest.java index 957b57a66435..dbc352d697a4 100644 --- a/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/resolution/ResolutionFeatureTest.java +++ b/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/resolution/ResolutionFeatureTest.java @@ -5,20 +5,27 @@ package io.flutter.plugins.camera.features.resolution; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.when; import android.media.CamcorderProfile; import android.media.EncoderProfiles; +import android.util.Size; import io.flutter.plugins.camera.CameraProperties; +import java.util.ArrayList; import java.util.List; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.MockedStatic; +import org.mockito.stubbing.Answer; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; @@ -329,4 +336,95 @@ public void computeBestPreviewSize_shouldUseQVGAWhenResolutionPresetLow() { mockedStaticProfile.verify(() -> CamcorderProfile.getAll("1", CamcorderProfile.QUALITY_QVGA)); } + + @Config(minSdk = 31) + @Test + public void computeBestPreviewSize_shouldUseLegacyBehaviorWhenEncoderProfilesNull() { + try (MockedStatic mockedResolutionFeature = + mockStatic(ResolutionFeature.class)) { + mockedResolutionFeature + .when( + () -> + ResolutionFeature.getBestAvailableCamcorderProfileForResolutionPreset( + anyInt(), any(ResolutionPreset.class))) + .thenAnswer( + (Answer) + invocation -> { + EncoderProfiles mockEncoderProfiles = mock(EncoderProfiles.class); + List videoProfiles = + new ArrayList() { + { + add(null); + } + }; + when(mockEncoderProfiles.getVideoProfiles()).thenReturn(videoProfiles); + return mockEncoderProfiles; + }); + mockedResolutionFeature + .when( + () -> + ResolutionFeature.getBestAvailableCamcorderProfileForResolutionPresetLegacy( + anyInt(), any(ResolutionPreset.class))) + .thenAnswer( + (Answer) + invocation -> { + CamcorderProfile mockCamcorderProfile = mock(CamcorderProfile.class); + mockCamcorderProfile.videoFrameWidth = 10; + mockCamcorderProfile.videoFrameHeight = 50; + return mockCamcorderProfile; + }); + mockedResolutionFeature + .when(() -> ResolutionFeature.computeBestPreviewSize(1, ResolutionPreset.max)) + .thenCallRealMethod(); + + Size testPreviewSize = ResolutionFeature.computeBestPreviewSize(1, ResolutionPreset.max); + assertEquals(testPreviewSize.getWidth(), 10); + assertEquals(testPreviewSize.getHeight(), 50); + } + } + + @Config(minSdk = 31) + @Test + public void resolutionFeatureShouldUseLegacyBehaviorWhenEncoderProfilesNull() { + beforeLegacy(); + try (MockedStatic mockedResolutionFeature = + mockStatic(ResolutionFeature.class)) { + mockedResolutionFeature + .when( + () -> + ResolutionFeature.getBestAvailableCamcorderProfileForResolutionPreset( + anyInt(), any(ResolutionPreset.class))) + .thenAnswer( + (Answer) + invocation -> { + EncoderProfiles mockEncoderProfiles = mock(EncoderProfiles.class); + List videoProfiles = + new ArrayList() { + { + add(null); + } + }; + when(mockEncoderProfiles.getVideoProfiles()).thenReturn(videoProfiles); + return mockEncoderProfiles; + }); + mockedResolutionFeature + .when( + () -> + ResolutionFeature.getBestAvailableCamcorderProfileForResolutionPresetLegacy( + anyInt(), any(ResolutionPreset.class))) + .thenAnswer( + (Answer) + invocation -> { + CamcorderProfile mockCamcorderProfile = mock(CamcorderProfile.class); + return mockCamcorderProfile; + }); + + CameraProperties mockCameraProperties = mock(CameraProperties.class); + ResolutionFeature resolutionFeature = + new ResolutionFeature(mockCameraProperties, ResolutionPreset.max, cameraName); + + assertNotNull(resolutionFeature.getRecordingProfileLegacy()); + assertNull(resolutionFeature.getRecordingProfile()); + } + } } diff --git a/packages/camera/camera_android/pubspec.yaml b/packages/camera/camera_android/pubspec.yaml index fed2d29fb59f..fb3371912911 100644 --- a/packages/camera/camera_android/pubspec.yaml +++ b/packages/camera/camera_android/pubspec.yaml @@ -2,7 +2,7 @@ name: camera_android description: Android implementation of the camera plugin. repository: https://github.com/flutter/plugins/tree/main/packages/camera/camera_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22 -version: 0.10.3 +version: 0.10.4 environment: sdk: ">=2.14.0 <3.0.0" From a59517a0334b17af76bd732487e9cb4756515f43 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Fri, 3 Feb 2023 15:20:44 -0500 Subject: [PATCH 069/130] Roll Flutter from d27880801435 to c5e8757fcb79 (54 revisions) (#7092) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 254a796bc Revert "Reland "Add --serve-observatory flag to run, attach, and test (#118402)" (#119529)" (flutter/flutter#119729) * 1573c1296 Marks Mac_android microbenchmarks to be unflaky (flutter/flutter#119727) * 5613ab010 remove unnecessary parens (flutter/flutter#119736) * 1305a509d Roll Flutter Engine from c08a286d60e9 to ba3adb74d952 (5 revisions) (flutter/flutter#119741) * 484d881f2 [conductor] update console link (flutter/flutter#118338) * 73124dcd3 Fix `ListTileThemeData.copyWith` doesn't override correct properties (flutter/flutter#119738) * bc8927c7d 2b8dd4e5c Use Windows high contrast black/white theme with `MaterialApp` themes (flutter/engine#39206) (flutter/flutter#119747) * 578edfc85 Catch errors thrown while handling pointer events (flutter/flutter#119577) * 8fd5d4ebb Remove deprecated SystemNavigator.routeUpdated method (flutter/flutter#119187) * 7a926dcb0 Deprecate MediaQuery[Data].fromWindow (flutter/flutter#119647) * cd118dada Update a test expectation that depended on an SkParagraph fix (flutter/flutter#119756) * 4ae2d3b6d 🔥 Do not format the messages file for `gen-l10n` (flutter/flutter#119596) * 8f5949eda Roll Flutter Engine from 2b8dd4e5c699 to a12d102773dd (2 revisions) (flutter/flutter#119758) * 4c99da6c5 Avoid printing blank lines between "Another exception was thrown:" messages. (flutter/flutter#119587) * 2ecf4ae96 Update the counter app to enable Material 3 (flutter/flutter#118835) * 0b8486dda 29d09845f Roll Dart SDK from b47964e5d575 to 1c219a91e637 (1 revision) (flutter/engine#39324) (flutter/flutter#119763) * ca7b7e3b0 Roll Flutter Engine from 29d09845f21e to 97d27ff59459 (2 revisions) (flutter/flutter#119765) * 475fc4ac9 Run Mac hostonly tests on any available arch (flutter/flutter#119762) * 322d10e1b 18118e10a Add iOS spring animation objc files (flutter/engine#38801) (flutter/flutter#119768) * f767f860e check if directory exists before listing content (flutter/flutter#119748) * 34730c79c Revert "Run Mac hostonly tests on any available arch (#119762)" (flutter/flutter#119784) * f9daa9aac Roll Flutter Engine from 18118e10a0a5 to 679c4b42e222 (4 revisions) (flutter/flutter#119783) * fd76ef0f2 Reland "Add API for discovering assets" (flutter/flutter#119277) * ca0596e41 Fix `pub get --unknown-flag` (flutter/flutter#119622) * 9abb6d707 Roll Plugins from 9da327ca39c4 to 9302d87ee545 (11 revisions) (flutter/flutter#119825) * 9eafbcc8b Roll Flutter Engine from 679c4b42e222 to 388890a98e5b (6 revisions) (flutter/flutter#119830) * 1ee87990d Revert "[Re-land] Exposed tooltip longPress (#118796)" (flutter/flutter#119832) * 07b51a0db Add missing variants and *new* indicators to `useMaterial3` docs (flutter/flutter#119799) * 201380ab2 e9e601c7c [web] Hide autofill overlay (flutter/engine#39294) (flutter/flutter#119833) * 1c0065c8d 27f55219d Roll Fuchsia Linux SDK from QxkjqmRgowkk_n2NZ... to pWloCaRzjLEAUvQEz... (flutter/engine#39339) (flutter/flutter#119838) * e7d934a01 Marks Linux_android android_choreographer_do_frame_test to be flaky (flutter/flutter#119721) * 3f986e423 Roll Flutter Engine from 27f55219df79 to ae38c9585a61 (2 revisions) (flutter/flutter#119840) * b0f1714b7 Make Flex,Row,Column const for real (flutter/flutter#119673) * d4b689847 [web] Put all index.html operations in one place (flutter/flutter#118188) * d63987f71 Parser machine logs (flutter/flutter#118707) * 22bbdf03e Android defines target update (flutter/flutter#119766) * 8387c2388 [flutter_tools] Use base DAP detach and ensure correct output (flutter/flutter#119076) * d820aec78 Manual pub roll with dwds fix (flutter/flutter#119575) * 72f9cf548 Roll Flutter Engine from ae38c9585a61 to 616ecd8be3de (3 revisions) (flutter/flutter#119859) * cfdc35859 roll packages (flutter/flutter#119865) * d875899a6 Bump test Chrome version for Mac arm support (flutter/flutter#119773) * 9b86a4853 Fix gets removedItem instead of its index (flutter/flutter#119638) * 66b2ca638 Roll Flutter Engine from 616ecd8be3de to 2871970337df (3 revisions) (flutter/flutter#119870) * c6264605d Make `_focusDebug` not interpolate in debug mode (flutter/flutter#119680) * a27802e2d flutter_tool: remove explicit length header in HTTP response (flutter/flutter#119869) * 3570cce58 Remove deprecated kind in GestureRecognizer et al (flutter/flutter#119572) * bc45b1858 2696fff87 Roll Skia from c2d81db3ef41 to 4f0166baf5a4 (13 revisions) (flutter/engine#39348) (flutter/flutter#119879) * f3effce63 Roll Flutter Engine from 2696fff8716d to e3fe6dade964 (3 revisions) (flutter/flutter#119892) * 69421c168 [framework] use shader tiling instead of repeated calls to drawImage (flutter/flutter#119495) * 6b83eff56 Roll Flutter Engine from e3fe6dade964 to 655530e3fd15 (5 revisions) (flutter/flutter#119905) * a5d8a4a72 67d35267c [Impeller] Use minimal coverage for stencil restores after overdraw prevention (flutter/engine#39358) (flutter/flutter#119910) * fc8ea5620 0fb48ce5b Roll Dart SDK from 69452c5012d9 to be795cc64bd7 (1 revision) (flutter/engine#39360) (flutter/flutter#119926) * e0b2138ba Dispose OverlayEntry in TooltipState. (flutter/flutter#117291) * c5e8757fc Add M3 support for iconbuttons in error state in TextFields (flutter/flutter#119925) --- .ci/flutter_master.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/flutter_master.version b/.ci/flutter_master.version index c91cdff3cd1e..339a8e70347e 100644 --- a/.ci/flutter_master.version +++ b/.ci/flutter_master.version @@ -1 +1 @@ -d27880801435109432dcada4a2245193af4ae1f2 +c5e8757fcb795867f9f1ef42d7c2e98a74d6a231 From bde2ff0d7484b7ddcd6d14c3e9ec04eeff9a1c8d Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Fri, 3 Feb 2023 16:17:46 -0500 Subject: [PATCH 070/130] Roll Flutter (stable) from b06b8b271095 to 7048ed95a5ad (5 revisions) (#7091) * 7999584b9 [release] use current branch as opposed to master (flutter/flutter#119648) * 9271eeba1 Switching over from iOS-15 to iOS-16 in .ci.yaml. (#118807) (flutter/flutter#118963) * c96f14ad3 Support safe area and scrolling in the NavigationDrawer (#116995) (flutter/flutter#119555) * ff6e9d4fa CP: Throw error when plural case had undefined behavior (#116622) (flutter/flutter#119384) * 7048ed95a Update Engine revision to 800594f1f4a6674010a6f1603c07a919b4d7ebd7 for stable release 3.7.1 (flutter/flutter#119687) --- .ci/flutter_stable.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/flutter_stable.version b/.ci/flutter_stable.version index 45363bc9d5e9..542569bcfd31 100644 --- a/.ci/flutter_stable.version +++ b/.ci/flutter_stable.version @@ -1 +1 @@ -b06b8b2710955028a6b562f5aa6fe62941d6febf +7048ed95a5ad3e43d697e0c397464193991fc230 From 1e58ab0f0a99cd817fd2f34943056b65298fbafa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 Feb 2023 22:34:59 +0000 Subject: [PATCH 071/130] [in_app_pur]: Bump billing from 5.0.0 to 5.1.0 in /packages/in_app_purchase/in_app_purchase_android/android (#6701) * [in_app_pur]: Bump billing Bumps billing from 5.0.0 to 5.1.0. --- updated-dependencies: - dependency-name: com.android.billingclient:billing dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * Add annotation and modify changelog --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: camsim99 --- .../in_app_purchase/in_app_purchase_android/CHANGELOG.md | 5 +++++ .../in_app_purchase_android/android/build.gradle | 4 ++-- .../in_app_purchase/in_app_purchase_android/pubspec.yaml | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md b/packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md index 4e199f2d03bf..76c94cbab35c 100644 --- a/packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md +++ b/packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.2.4+1 + +* Updates Google Play Billing Library to 5.1.0. +* Updates androidx.annotation to 1.5.0. + ## 0.2.4 * Updates minimum Flutter version to 3.0. diff --git a/packages/in_app_purchase/in_app_purchase_android/android/build.gradle b/packages/in_app_purchase/in_app_purchase_android/android/build.gradle index 16be43200bb4..fe9a958580ba 100644 --- a/packages/in_app_purchase/in_app_purchase_android/android/build.gradle +++ b/packages/in_app_purchase/in_app_purchase_android/android/build.gradle @@ -54,8 +54,8 @@ android { } dependencies { - implementation 'androidx.annotation:annotation:1.3.0' - implementation 'com.android.billingclient:billing:5.0.0' + implementation 'androidx.annotation:annotation:1.5.0' + implementation 'com.android.billingclient:billing:5.1.0' testImplementation 'junit:junit:4.13.2' testImplementation 'org.json:json:20220924' testImplementation 'org.mockito:mockito-core:4.7.0' diff --git a/packages/in_app_purchase/in_app_purchase_android/pubspec.yaml b/packages/in_app_purchase/in_app_purchase_android/pubspec.yaml index e02cb66627cf..397e82a82446 100644 --- a/packages/in_app_purchase/in_app_purchase_android/pubspec.yaml +++ b/packages/in_app_purchase/in_app_purchase_android/pubspec.yaml @@ -2,7 +2,7 @@ name: in_app_purchase_android description: An implementation for the Android platform of the Flutter `in_app_purchase` plugin. This uses the Android BillingClient APIs. repository: https://github.com/flutter/plugins/tree/main/packages/in_app_purchase/in_app_purchase_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+in_app_purchase%22 -version: 0.2.4 +version: 0.2.4+1 environment: sdk: ">=2.14.0 <3.0.0" From a5f40d751594e2e6a3722fa11d8259663868784e Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Sun, 5 Feb 2023 10:45:48 -0500 Subject: [PATCH 072/130] Roll Flutter from c5e8757fcb79 to b8f5394a5ca6 (22 revisions) (#7105) * 7177c413a Add Material 3 `RadioListTile` example and update existing examples (flutter/flutter#119716) * 96c8c6974 Roll Flutter Engine from 0fb48ce5b118 to c39047ffb2a6 (2 revisions) (flutter/flutter#119939) * 00b0d550c Fix iOS context menu position when flipped below (flutter/flutter#119565) * b65ae62cf Roll Flutter Engine from c39047ffb2a6 to 745d7efb5736 (3 revisions) (flutter/flutter#119943) * be4c8c0eb 33d932efc Add gen_snapshot to windows flutter artifact. (flutter/engine#39353) (flutter/flutter#119951) * 9a7e18701 [flutter_tools] fix Cannot delete file ENOENT from fuchsia_asset_builder (flutter/flutter#119867) * 16f81e656 Roll Flutter Engine from 33d932efc68e to 110c643d6ac2 (3 revisions) (flutter/flutter#119957) * 51b05ac7e Add mac_benchmark ci.yaml property (flutter/flutter#119871) * 3f02d4b4c Tweak to floating-cursor-end behaviour (flutter/flutter#119893) * c52215e93 Run Mac hostonly tests on any available arch (flutter/flutter#119884) * c24904dde Run macOS benchmarks in prod pool to upload metrics (flutter/flutter#119963) * 0fbef4693 e1b265bb5 Roll Skia from 07a95bb37760 to 83a3d8b16c94 (5 revisions) (flutter/engine#39373) (flutter/flutter#119967) * 909dc3009 Verify Mac artifact codesigning on x64 and arm64 (flutter/flutter#119971) * 57fd50f84 Fix unable to find bundled Java version (flutter/flutter#119244) * f7c2bd05f Revert "Fix unable to find bundled Java version (#119244)" (flutter/flutter#119981) * c8e75a8df Do not run customer testing on release candidate branches. (flutter/flutter#119979) * 5241d38fa Roll Flutter Engine from e1b265bb52aa to 1b132e44194d (8 revisions) (flutter/flutter#119980) * 61f6a0bcd Roll Flutter Engine from 1b132e44194d to c7a4bbab0e75 (6 revisions) (flutter/flutter#119990) * f10e625eb De-flake adapter integration test (flutter/flutter#120016) * 5187b45e4 Roll Flutter Engine from c7a4bbab0e75 to 6bd500c38ea8 (2 revisions) (flutter/flutter#120018) * 2e39badf9 Roll Flutter Engine from 6bd500c38ea8 to 2a104cdfcdf8 (2 revisions) (flutter/flutter#120022) * b8f5394a5 [flutter_tools] Fix Future error handling ArgumentError in doctor --android-licenses (flutter/flutter#119977) --- .ci/flutter_master.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/flutter_master.version b/.ci/flutter_master.version index 339a8e70347e..52127006863e 100644 --- a/.ci/flutter_master.version +++ b/.ci/flutter_master.version @@ -1 +1 @@ -c5e8757fcb795867f9f1ef42d7c2e98a74d6a231 +b8f5394a5ca6d2bce062d9d0a20aaffb4289fb4c From d065e4e0a82ab770895ac9caee73503f45b3b445 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Feb 2023 02:33:06 +0000 Subject: [PATCH 073/130] [gh_actions]: Bump actions/upload-artifact from 3.1.1 to 3.1.2 (#6936) Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 3.1.1 to 3.1.2. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/83fd05a356d7e2593de66fc9913b3002723633cb...0b7f8abb1508181956e8e162db84b466c27e18ce) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/scorecards-analysis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scorecards-analysis.yml b/.github/workflows/scorecards-analysis.yml index f50f8d09dcd5..dacb2d3a6f59 100644 --- a/.github/workflows/scorecards-analysis.yml +++ b/.github/workflows/scorecards-analysis.yml @@ -42,7 +42,7 @@ jobs: # Upload the results as artifacts (optional). - name: "Upload artifact" - uses: actions/upload-artifact@83fd05a356d7e2593de66fc9913b3002723633cb + uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce with: name: SARIF file path: results.sarif From 88386459987f87ed6a1f916fcafd15376c7df6f4 Mon Sep 17 00:00:00 2001 From: Victoria Ashworth Date: Mon, 6 Feb 2023 11:39:06 -0600 Subject: [PATCH 074/130] [image_picker] GIF files will animate without permissions. PNG and GIF files will retain their image type if missing permissions. (#7084) * Fix GIF files not animating if permission not given. Fix PNG files getting convert to JPG if permission not given * updated changelog and pubspec, added a comment --- .../image_picker_ios/CHANGELOG.md | 3 +- .../PickerSaveImageToPathOperationTests.m | 60 +++++++++++++++---- .../FLTPHPickerSaveImageToPathOperation.m | 16 +++-- .../image_picker_ios/pubspec.yaml | 2 +- 4 files changed, 60 insertions(+), 21 deletions(-) diff --git a/packages/image_picker/image_picker_ios/CHANGELOG.md b/packages/image_picker/image_picker_ios/CHANGELOG.md index 04bb4dfdf890..b75e6333b94e 100644 --- a/packages/image_picker/image_picker_ios/CHANGELOG.md +++ b/packages/image_picker/image_picker_ios/CHANGELOG.md @@ -1,5 +1,6 @@ -## NEXT +## 0.8.6+7 +* Fixes issue where GIF file would not animate without `Photo Library Usage` permissions. Fixes issue where PNG and GIF files were converted to JPG, but only when they are do not have `Photo Library Usage` permissions. * Updates minimum Flutter version to 3.0. ## 0.8.6+6 diff --git a/packages/image_picker/image_picker_ios/example/ios/RunnerTests/PickerSaveImageToPathOperationTests.m b/packages/image_picker/image_picker_ios/example/ios/RunnerTests/PickerSaveImageToPathOperationTests.m index 091755ca163b..027e287d1586 100644 --- a/packages/image_picker/image_picker_ios/example/ios/RunnerTests/PickerSaveImageToPathOperationTests.m +++ b/packages/image_picker/image_picker_ios/example/ios/RunnerTests/PickerSaveImageToPathOperationTests.m @@ -21,7 +21,7 @@ - (void)testSaveWebPImage API_AVAILABLE(ios(14)) { NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithContentsOfURL:imageURL]; PHPickerResult *result = [self createPickerResultWithProvider:itemProvider]; - [self verifySavingImageWithPickerResult:result fullMetadata:YES]; + [self verifySavingImageWithPickerResult:result fullMetadata:YES withExtension:@"jpg"]; } - (void)testSavePNGImage API_AVAILABLE(ios(14)) { @@ -30,7 +30,7 @@ - (void)testSavePNGImage API_AVAILABLE(ios(14)) { NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithContentsOfURL:imageURL]; PHPickerResult *result = [self createPickerResultWithProvider:itemProvider]; - [self verifySavingImageWithPickerResult:result fullMetadata:YES]; + [self verifySavingImageWithPickerResult:result fullMetadata:YES withExtension:@"png"]; } - (void)testSaveJPGImage API_AVAILABLE(ios(14)) { @@ -39,7 +39,7 @@ - (void)testSaveJPGImage API_AVAILABLE(ios(14)) { NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithContentsOfURL:imageURL]; PHPickerResult *result = [self createPickerResultWithProvider:itemProvider]; - [self verifySavingImageWithPickerResult:result fullMetadata:YES]; + [self verifySavingImageWithPickerResult:result fullMetadata:YES withExtension:@"jpg"]; } - (void)testSaveGIFImage API_AVAILABLE(ios(14)) { @@ -48,7 +48,39 @@ - (void)testSaveGIFImage API_AVAILABLE(ios(14)) { NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithContentsOfURL:imageURL]; PHPickerResult *result = [self createPickerResultWithProvider:itemProvider]; - [self verifySavingImageWithPickerResult:result fullMetadata:YES]; + NSData *dataGIF = [NSData dataWithContentsOfURL:imageURL]; + CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)dataGIF, nil); + size_t numberOfFrames = CGImageSourceGetCount(imageSource); + + XCTestExpectation *pathExpectation = [self expectationWithDescription:@"Path was created"]; + XCTestExpectation *operationExpectation = + [self expectationWithDescription:@"Operation completed"]; + + FLTPHPickerSaveImageToPathOperation *operation = [[FLTPHPickerSaveImageToPathOperation alloc] + initWithResult:result + maxHeight:@100 + maxWidth:@100 + desiredImageQuality:@100 + fullMetadata:NO + savedPathBlock:^(NSString *savedPath, FlutterError *error) { + XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:savedPath]); + + // Ensure gif is animated. + XCTAssertEqualObjects([NSURL URLWithString:savedPath].pathExtension, @"gif"); + NSData *newDataGIF = [NSData dataWithContentsOfFile:savedPath]; + CGImageSourceRef newImageSource = + CGImageSourceCreateWithData((__bridge CFDataRef)newDataGIF, nil); + size_t newNumberOfFrames = CGImageSourceGetCount(newImageSource); + XCTAssertEqual(numberOfFrames, newNumberOfFrames); + [pathExpectation fulfill]; + }]; + operation.completionBlock = ^{ + [operationExpectation fulfill]; + }; + + [operation start]; + [self waitForExpectationsWithTimeout:30 handler:nil]; + XCTAssertTrue(operation.isFinished); } - (void)testSaveBMPImage API_AVAILABLE(ios(14)) { @@ -57,7 +89,7 @@ - (void)testSaveBMPImage API_AVAILABLE(ios(14)) { NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithContentsOfURL:imageURL]; PHPickerResult *result = [self createPickerResultWithProvider:itemProvider]; - [self verifySavingImageWithPickerResult:result fullMetadata:YES]; + [self verifySavingImageWithPickerResult:result fullMetadata:YES withExtension:@"jpg"]; } - (void)testSaveHEICImage API_AVAILABLE(ios(14)) { @@ -66,7 +98,7 @@ - (void)testSaveHEICImage API_AVAILABLE(ios(14)) { NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithContentsOfURL:imageURL]; PHPickerResult *result = [self createPickerResultWithProvider:itemProvider]; - [self verifySavingImageWithPickerResult:result fullMetadata:YES]; + [self verifySavingImageWithPickerResult:result fullMetadata:YES withExtension:@"jpg"]; } - (void)testSaveICNSImage API_AVAILABLE(ios(14)) { @@ -75,7 +107,7 @@ - (void)testSaveICNSImage API_AVAILABLE(ios(14)) { NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithContentsOfURL:imageURL]; PHPickerResult *result = [self createPickerResultWithProvider:itemProvider]; - [self verifySavingImageWithPickerResult:result fullMetadata:YES]; + [self verifySavingImageWithPickerResult:result fullMetadata:YES withExtension:@"jpg"]; } - (void)testSaveICOImage API_AVAILABLE(ios(14)) { @@ -84,7 +116,7 @@ - (void)testSaveICOImage API_AVAILABLE(ios(14)) { NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithContentsOfURL:imageURL]; PHPickerResult *result = [self createPickerResultWithProvider:itemProvider]; - [self verifySavingImageWithPickerResult:result fullMetadata:YES]; + [self verifySavingImageWithPickerResult:result fullMetadata:YES withExtension:@"jpg"]; } - (void)testSaveProRAWImage API_AVAILABLE(ios(14)) { @@ -93,7 +125,7 @@ - (void)testSaveProRAWImage API_AVAILABLE(ios(14)) { NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithContentsOfURL:imageURL]; PHPickerResult *result = [self createPickerResultWithProvider:itemProvider]; - [self verifySavingImageWithPickerResult:result fullMetadata:YES]; + [self verifySavingImageWithPickerResult:result fullMetadata:YES withExtension:@"jpg"]; } - (void)testSaveSVGImage API_AVAILABLE(ios(14)) { @@ -102,7 +134,7 @@ - (void)testSaveSVGImage API_AVAILABLE(ios(14)) { NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithContentsOfURL:imageURL]; PHPickerResult *result = [self createPickerResultWithProvider:itemProvider]; - [self verifySavingImageWithPickerResult:result fullMetadata:YES]; + [self verifySavingImageWithPickerResult:result fullMetadata:YES withExtension:@"jpg"]; } - (void)testSaveTIFFImage API_AVAILABLE(ios(14)) { @@ -110,7 +142,7 @@ - (void)testSaveTIFFImage API_AVAILABLE(ios(14)) { withExtension:@"tiff"]; NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithContentsOfURL:imageURL]; PHPickerResult *result = [self createPickerResultWithProvider:itemProvider]; - [self verifySavingImageWithPickerResult:result fullMetadata:YES]; + [self verifySavingImageWithPickerResult:result fullMetadata:YES withExtension:@"jpg"]; } - (void)testNonexistentImage API_AVAILABLE(ios(14)) { @@ -176,7 +208,7 @@ - (void)testSavePNGImageWithoutFullMetadata API_AVAILABLE(ios(14)) { PHPickerResult *result = [self createPickerResultWithProvider:itemProvider]; OCMReject([photoAssetUtil fetchAssetsWithLocalIdentifiers:OCMOCK_ANY options:OCMOCK_ANY]); - [self verifySavingImageWithPickerResult:result fullMetadata:NO]; + [self verifySavingImageWithPickerResult:result fullMetadata:NO withExtension:@"png"]; OCMVerifyAll(photoAssetUtil); } @@ -204,7 +236,8 @@ - (PHPickerResult *)createPickerResultWithProvider:(NSItemProvider *)itemProvide * @param result the picker result */ - (void)verifySavingImageWithPickerResult:(PHPickerResult *)result - fullMetadata:(BOOL)fullMetadata API_AVAILABLE(ios(14)) { + fullMetadata:(BOOL)fullMetadata + withExtension:(NSString *)extension API_AVAILABLE(ios(14)) { XCTestExpectation *pathExpectation = [self expectationWithDescription:@"Path was created"]; XCTestExpectation *operationExpectation = [self expectationWithDescription:@"Operation completed"]; @@ -217,6 +250,7 @@ - (void)verifySavingImageWithPickerResult:(PHPickerResult *)result fullMetadata:fullMetadata savedPathBlock:^(NSString *savedPath, FlutterError *error) { XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:savedPath]); + XCTAssertEqualObjects([NSURL URLWithString:savedPath].pathExtension, extension); [pathExpectation fulfill]; }]; operation.completionBlock = ^{ diff --git a/packages/image_picker/image_picker_ios/ios/Classes/FLTPHPickerSaveImageToPathOperation.m b/packages/image_picker/image_picker_ios/ios/Classes/FLTPHPickerSaveImageToPathOperation.m index 11fedfb73846..efcbdbeec897 100644 --- a/packages/image_picker/image_picker_ios/ios/Classes/FLTPHPickerSaveImageToPathOperation.m +++ b/packages/image_picker/image_picker_ios/ios/Classes/FLTPHPickerSaveImageToPathOperation.m @@ -98,8 +98,7 @@ - (void)start { completionHandler:^(NSData *_Nullable data, NSError *_Nullable error) { if (data != nil) { - UIImage *image = [[UIImage alloc] initWithData:data]; - [self processImage:image]; + [self processImage:data]; } else { FlutterError *flutterError = [FlutterError errorWithCode:@"invalid_image" @@ -122,7 +121,9 @@ - (void)start { /** * Processes the image. */ -- (void)processImage:(UIImage *)localImage API_AVAILABLE(ios(14)) { +- (void)processImage:(NSData *)pickerImageData API_AVAILABLE(ios(14)) { + UIImage *localImage = [[UIImage alloc] initWithData:pickerImageData]; + PHAsset *originalAsset; // Only if requested, fetch the full "PHAsset" metadata, which requires "Photo Library Usage" // permissions. @@ -172,10 +173,13 @@ - (void)processImage:(UIImage *)localImage API_AVAILABLE(ios(14)) { } } else { // Image picked without an original asset (e.g. User pick image without permission) + // maxWidth and maxHeight are used only for GIF images. NSString *savedPath = - [FLTImagePickerPhotoAssetUtil saveImageWithPickerInfo:nil - image:localImage - imageQuality:self.desiredImageQuality]; + [FLTImagePickerPhotoAssetUtil saveImageWithOriginalImageData:pickerImageData + image:localImage + maxWidth:self.maxWidth + maxHeight:self.maxHeight + imageQuality:self.desiredImageQuality]; [self completeOperationWithPath:savedPath error:nil]; } } diff --git a/packages/image_picker/image_picker_ios/pubspec.yaml b/packages/image_picker/image_picker_ios/pubspec.yaml index 5bfb8852d2cc..1f4e2af4cb96 100755 --- a/packages/image_picker/image_picker_ios/pubspec.yaml +++ b/packages/image_picker/image_picker_ios/pubspec.yaml @@ -2,7 +2,7 @@ name: image_picker_ios description: iOS implementation of the image_picker plugin. repository: https://github.com/flutter/plugins/tree/main/packages/image_picker/image_picker_ios issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+image_picker%22 -version: 0.8.6+6 +version: 0.8.6+7 environment: sdk: ">=2.14.0 <3.0.0" From 7a63dbc8f3fd39bbcd1984be51ced145d07e81ca Mon Sep 17 00:00:00 2001 From: Braden Bagby <33461698+BradenBagby@users.noreply.github.com> Date: Mon, 6 Feb 2023 10:54:08 -0700 Subject: [PATCH 075/130] [camera] flip/change camera while recording - platform interface (#7011) * platform interface changes pr * changes version to 2.4 * fixes versioning --------- Co-authored-by: BradenBagby --- .../camera_platform_interface/CHANGELOG.md | 3 ++- .../method_channel/method_channel_camera.dart | 11 +++++++++ .../platform_interface/camera_platform.dart | 6 +++++ .../camera_platform_interface/pubspec.yaml | 2 +- .../method_channel_camera_test.dart | 23 +++++++++++++++++++ 5 files changed, 43 insertions(+), 2 deletions(-) diff --git a/packages/camera/camera_platform_interface/CHANGELOG.md b/packages/camera/camera_platform_interface/CHANGELOG.md index 64fb555a99de..b51eb9c78a43 100644 --- a/packages/camera/camera_platform_interface/CHANGELOG.md +++ b/packages/camera/camera_platform_interface/CHANGELOG.md @@ -1,5 +1,6 @@ -## NEXT +## 2.4.0 +* Allows camera to be switched while video recording. * Updates minimum Flutter version to 3.0. ## 2.3.4 diff --git a/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart b/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart index f770499c755a..14d20fc817b2 100644 --- a/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart +++ b/packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart @@ -504,6 +504,17 @@ class MethodChannelCamera extends CameraPlatform { ); } + @override + Future setDescriptionWhileRecording( + CameraDescription description) async { + await _channel.invokeMethod( + 'setDescriptionWhileRecording', + { + 'cameraName': description.name, + }, + ); + } + @override Widget buildPreview(int cameraId) { return Texture(textureId: cameraId); diff --git a/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart b/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart index b3e5b8f82afa..b43629d4e0c3 100644 --- a/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart +++ b/packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart @@ -269,6 +269,12 @@ abstract class CameraPlatform extends PlatformInterface { throw UnimplementedError('pausePreview() is not implemented.'); } + /// Sets the active camera while recording. + Future setDescriptionWhileRecording(CameraDescription description) { + throw UnimplementedError( + 'setDescriptionWhileRecording() is not implemented.'); + } + /// Returns a widget showing a live camera preview. Widget buildPreview(int cameraId) { throw UnimplementedError('buildView() has not been implemented.'); diff --git a/packages/camera/camera_platform_interface/pubspec.yaml b/packages/camera/camera_platform_interface/pubspec.yaml index ff024c0404ad..4cdb2855a156 100644 --- a/packages/camera/camera_platform_interface/pubspec.yaml +++ b/packages/camera/camera_platform_interface/pubspec.yaml @@ -4,7 +4,7 @@ repository: https://github.com/flutter/plugins/tree/main/packages/camera/camera_ issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22 # 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: 2.3.4 +version: 2.4.0 environment: sdk: '>=2.12.0 <3.0.0' diff --git a/packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart b/packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart index ed6151522f0c..b01123d7cb29 100644 --- a/packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart +++ b/packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart @@ -576,6 +576,29 @@ void main() { ]); }); + test('Should set description while recording', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: 'plugins.flutter.io/camera', + methods: {'setDescriptionWhileRecording': null}, + ); + + // Act + const CameraDescription cameraDescription = CameraDescription( + name: 'Test', + lensDirection: CameraLensDirection.back, + sensorOrientation: 0); + await camera.setDescriptionWhileRecording(cameraDescription); + + // Assert + expect(channel.log, [ + isMethodCall('setDescriptionWhileRecording', + arguments: { + 'cameraName': cameraDescription.name + }), + ]); + }); + test('Should pass maxVideoDuration when starting recording a video', () async { // Arrange From c6be93605c8714dff535fef23da1b10b7348c80e Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Mon, 6 Feb 2023 12:55:50 -0500 Subject: [PATCH 076/130] Roll Flutter from b8f5394a5ca6 to 3c3c9a1bd98f (3 revisions) (#7107) * bbca7ff69 Add Material 3 `SwitchListTile` example and update existing examples (flutter/flutter#119714) * 47a067465 Reland "Add --serve-observatory flag to run, attach, and test (#118402)" (flutter/flutter#119737) * 3c3c9a1bd [M3] Add ListTile's iconColor property support for icon buttons (flutter/flutter#120075) --- .ci/flutter_master.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/flutter_master.version b/.ci/flutter_master.version index 52127006863e..3189386f5fbf 100644 --- a/.ci/flutter_master.version +++ b/.ci/flutter_master.version @@ -1 +1 @@ -b8f5394a5ca6d2bce062d9d0a20aaffb4289fb4c +3c3c9a1bd98f464fc2c25751dac345dd61545ebd From eb0a6b5622f99f7f82b2a2d42c2206d4a52dc9b4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Feb 2023 17:55:53 +0000 Subject: [PATCH 077/130] [gh_actions]: Bump github/codeql-action from 2.1.37 to 2.2.1 (#7059) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 2.1.37 to 2.2.1. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/959cbb7472c4d4ad70cdfe6f4976053fe48ab394...3ebbd71c74ef574dbc558c82f70e52732c8b44fe) --- updated-dependencies: - dependency-name: github/codeql-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/scorecards-analysis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scorecards-analysis.yml b/.github/workflows/scorecards-analysis.yml index dacb2d3a6f59..f0f36ab9d96c 100644 --- a/.github/workflows/scorecards-analysis.yml +++ b/.github/workflows/scorecards-analysis.yml @@ -50,6 +50,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@959cbb7472c4d4ad70cdfe6f4976053fe48ab394 + uses: github/codeql-action/upload-sarif@3ebbd71c74ef574dbc558c82f70e52732c8b44fe with: sarif_file: results.sarif From dc0d3a02b66a4d9c3b806a7df9e0e91157e7abab Mon Sep 17 00:00:00 2001 From: Camille Simon <43054281+camsim99@users.noreply.github.com> Date: Mon, 6 Feb 2023 10:53:14 -0800 Subject: [PATCH 078/130] [camerax] Wrap methods necessary for preview implementation (#7046) * Add code needed from proof of concept * Add test files, delete unecessary method * Add tests, remove unecessary code * Fix analyze * Update changelog * Cleanup: * Cleanup and add switch * Finish todo * Add onCameraError * Fix pigeon file * Add method for releasing flutter texture and cleanup surface logic * Add test for release method * Add dart test * Update changelog * Modify flutter api names to avoid stack overflow * Cleanup * Fix tests * Delete space * Address review 1 * Update switch * Add annotations and constants in tests * Reset verification behavior --- .../camera_android_camerax/CHANGELOG.md | 1 + .../camerax/CameraAndroidCameraxPlugin.java | 2 + .../flutter/plugins/camerax/CameraXProxy.java | 33 ++- .../camerax/GeneratedCameraXLibrary.java | 255 ++++++++++++++++++ .../plugins/camerax/PreviewHostApiImpl.java | 149 ++++++++++ .../camerax/SystemServicesFlutterApiImpl.java | 14 +- .../camerax/SystemServicesHostApiImpl.java | 5 +- .../flutter/plugins/camerax/PreviewTest.java | 221 +++++++++++++++ .../plugins/camerax/SystemServicesTest.java | 3 +- .../lib/src/camerax_library.g.dart | 195 ++++++++++++++ .../lib/src/preview.dart | 126 +++++++++ .../lib/src/surface.dart | 34 +++ .../lib/src/system_services.dart | 14 +- .../pigeons/camerax_library.dart | 23 ++ .../test/preview_test.dart | 138 ++++++++++ .../test/preview_test.mocks.dart | 89 ++++++ .../test/system_services_test.dart | 9 + .../test/test_camerax_library.g.dart | 117 ++++++++ 18 files changed, 1413 insertions(+), 15 deletions(-) create mode 100644 packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/PreviewHostApiImpl.java create mode 100644 packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/PreviewTest.java create mode 100644 packages/camera/camera_android_camerax/lib/src/preview.dart create mode 100644 packages/camera/camera_android_camerax/lib/src/surface.dart create mode 100644 packages/camera/camera_android_camerax/test/preview_test.dart create mode 100644 packages/camera/camera_android_camerax/test/preview_test.mocks.dart diff --git a/packages/camera/camera_android_camerax/CHANGELOG.md b/packages/camera/camera_android_camerax/CHANGELOG.md index d5355c60c751..080240a64f42 100644 --- a/packages/camera/camera_android_camerax/CHANGELOG.md +++ b/packages/camera/camera_android_camerax/CHANGELOG.md @@ -8,3 +8,4 @@ * Adds Camera and UseCase classes, along with methods for binding UseCases to a lifecycle with the ProcessCameraProvider. * Bump CameraX version to 1.3.0-alpha03 and Kotlin version to 1.8.0. * Changes instance manager to allow the separate creation of identical objects. +* Adds Preview and Surface classes, along with other methods needed to implement camera preview. diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraAndroidCameraxPlugin.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraAndroidCameraxPlugin.java index c35394f01d82..b61e7ac72224 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraAndroidCameraxPlugin.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraAndroidCameraxPlugin.java @@ -49,6 +49,8 @@ void setUp(BinaryMessenger binaryMessenger, Context context, TextureRegistry tex binaryMessenger, processCameraProviderHostApi); systemServicesHostApi = new SystemServicesHostApiImpl(binaryMessenger, instanceManager); GeneratedCameraXLibrary.SystemServicesHostApi.setup(binaryMessenger, systemServicesHostApi); + GeneratedCameraXLibrary.PreviewHostApi.setup( + binaryMessenger, new PreviewHostApiImpl(binaryMessenger, instanceManager, textureRegistry)); } @Override diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraXProxy.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraXProxy.java index 83c43a9d55d4..4a3d277a4dc3 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraXProxy.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraXProxy.java @@ -5,8 +5,14 @@ package io.flutter.plugins.camerax; import android.app.Activity; +import android.graphics.SurfaceTexture; +import android.view.Surface; +import androidx.annotation.NonNull; import androidx.camera.core.CameraSelector; +import androidx.camera.core.Preview; +import io.flutter.plugin.common.BinaryMessenger; +/** Utility class used to create CameraX-related objects primarily for testing purposes. */ public class CameraXProxy { public CameraSelector.Builder createCameraSelectorBuilder() { return new CameraSelector.Builder(); @@ -17,10 +23,29 @@ public CameraPermissionsManager createCameraPermissionsManager() { } public DeviceOrientationManager createDeviceOrientationManager( - Activity activity, - Boolean isFrontFacing, - int sensorOrientation, - DeviceOrientationManager.DeviceOrientationChangeCallback callback) { + @NonNull Activity activity, + @NonNull Boolean isFrontFacing, + @NonNull int sensorOrientation, + @NonNull DeviceOrientationManager.DeviceOrientationChangeCallback callback) { return new DeviceOrientationManager(activity, isFrontFacing, sensorOrientation, callback); } + + public Preview.Builder createPreviewBuilder() { + return new Preview.Builder(); + } + + public Surface createSurface(@NonNull SurfaceTexture surfaceTexture) { + return new Surface(surfaceTexture); + } + + /** + * Creates an instance of the {@code SystemServicesFlutterApiImpl}. + * + *

Included in this class to utilize the callback methods it provides, e.g. {@code + * onCameraError(String)}. + */ + public SystemServicesFlutterApiImpl createSystemServicesFlutterApiImpl( + @NonNull BinaryMessenger binaryMessenger) { + return new SystemServicesFlutterApiImpl(binaryMessenger); + } } diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/GeneratedCameraXLibrary.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/GeneratedCameraXLibrary.java index 528870cc749c..1e61ea699292 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/GeneratedCameraXLibrary.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/GeneratedCameraXLibrary.java @@ -25,6 +25,82 @@ @SuppressWarnings({"unused", "unchecked", "CodeBlock2Expr", "RedundantSuppression"}) public class GeneratedCameraXLibrary { + /** Generated class from Pigeon that represents data sent in messages. */ + public static class ResolutionInfo { + private @NonNull Long width; + + public @NonNull Long getWidth() { + return width; + } + + public void setWidth(@NonNull Long setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"width\" is null."); + } + this.width = setterArg; + } + + private @NonNull Long height; + + public @NonNull Long getHeight() { + return height; + } + + public void setHeight(@NonNull Long setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"height\" is null."); + } + this.height = setterArg; + } + + /** Constructor is private to enforce null safety; use Builder. */ + private ResolutionInfo() {} + + public static final class Builder { + private @Nullable Long width; + + public @NonNull Builder setWidth(@NonNull Long setterArg) { + this.width = setterArg; + return this; + } + + private @Nullable Long height; + + public @NonNull Builder setHeight(@NonNull Long setterArg) { + this.height = setterArg; + return this; + } + + public @NonNull ResolutionInfo build() { + ResolutionInfo pigeonReturn = new ResolutionInfo(); + pigeonReturn.setWidth(width); + pigeonReturn.setHeight(height); + return pigeonReturn; + } + } + + @NonNull + Map toMap() { + Map toMapResult = new HashMap<>(); + toMapResult.put("width", width); + toMapResult.put("height", height); + return toMapResult; + } + + static @NonNull ResolutionInfo fromMap(@NonNull Map map) { + ResolutionInfo pigeonResult = new ResolutionInfo(); + Object width = map.get("width"); + pigeonResult.setWidth( + (width == null) ? null : ((width instanceof Integer) ? (Integer) width : (Long) width)); + Object height = map.get("height"); + pigeonResult.setHeight( + (height == null) + ? null + : ((height instanceof Integer) ? (Integer) height : (Long) height)); + return pigeonResult; + } + } + /** Generated class from Pigeon that represents data sent in messages. */ public static class CameraPermissionsErrorData { private @NonNull String errorCode; @@ -843,6 +919,185 @@ public void onDeviceOrientationChanged(@NonNull String orientationArg, Reply callback) { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.SystemServicesFlutterApi.onCameraError", + getCodec()); + channel.send( + new ArrayList(Arrays.asList(errorDescriptionArg)), + channelReply -> { + callback.reply(null); + }); + } + } + + private static class PreviewHostApiCodec extends StandardMessageCodec { + public static final PreviewHostApiCodec INSTANCE = new PreviewHostApiCodec(); + + private PreviewHostApiCodec() {} + + @Override + protected Object readValueOfType(byte type, ByteBuffer buffer) { + switch (type) { + case (byte) 128: + return ResolutionInfo.fromMap((Map) readValue(buffer)); + + case (byte) 129: + return ResolutionInfo.fromMap((Map) readValue(buffer)); + + default: + return super.readValueOfType(type, buffer); + } + } + + @Override + protected void writeValue(ByteArrayOutputStream stream, Object value) { + if (value instanceof ResolutionInfo) { + stream.write(128); + writeValue(stream, ((ResolutionInfo) value).toMap()); + } else if (value instanceof ResolutionInfo) { + stream.write(129); + writeValue(stream, ((ResolutionInfo) value).toMap()); + } else { + super.writeValue(stream, value); + } + } + } + + /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ + public interface PreviewHostApi { + void create( + @NonNull Long identifier, + @Nullable Long rotation, + @Nullable ResolutionInfo targetResolution); + + @NonNull + Long setSurfaceProvider(@NonNull Long identifier); + + void releaseFlutterSurfaceTexture(); + + @NonNull + ResolutionInfo getResolutionInfo(@NonNull Long identifier); + + /** The codec used by PreviewHostApi. */ + static MessageCodec getCodec() { + return PreviewHostApiCodec.INSTANCE; + } + + /** Sets up an instance of `PreviewHostApi` to handle messages through the `binaryMessenger`. */ + static void setup(BinaryMessenger binaryMessenger, PreviewHostApi api) { + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, "dev.flutter.pigeon.PreviewHostApi.create", getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + Map wrapped = new HashMap<>(); + try { + ArrayList args = (ArrayList) message; + Number identifierArg = (Number) args.get(0); + if (identifierArg == null) { + throw new NullPointerException("identifierArg unexpectedly null."); + } + Number rotationArg = (Number) args.get(1); + ResolutionInfo targetResolutionArg = (ResolutionInfo) args.get(2); + api.create( + (identifierArg == null) ? null : identifierArg.longValue(), + (rotationArg == null) ? null : rotationArg.longValue(), + targetResolutionArg); + wrapped.put("result", null); + } catch (Error | RuntimeException exception) { + wrapped.put("error", wrapError(exception)); + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.PreviewHostApi.setSurfaceProvider", + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + Map wrapped = new HashMap<>(); + try { + ArrayList args = (ArrayList) message; + Number identifierArg = (Number) args.get(0); + if (identifierArg == null) { + throw new NullPointerException("identifierArg unexpectedly null."); + } + Long output = + api.setSurfaceProvider( + (identifierArg == null) ? null : identifierArg.longValue()); + wrapped.put("result", output); + } catch (Error | RuntimeException exception) { + wrapped.put("error", wrapError(exception)); + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.PreviewHostApi.releaseFlutterSurfaceTexture", + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + Map wrapped = new HashMap<>(); + try { + api.releaseFlutterSurfaceTexture(); + wrapped.put("result", null); + } catch (Error | RuntimeException exception) { + wrapped.put("error", wrapError(exception)); + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, "dev.flutter.pigeon.PreviewHostApi.getResolutionInfo", getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + Map wrapped = new HashMap<>(); + try { + ArrayList args = (ArrayList) message; + Number identifierArg = (Number) args.get(0); + if (identifierArg == null) { + throw new NullPointerException("identifierArg unexpectedly null."); + } + ResolutionInfo output = + api.getResolutionInfo( + (identifierArg == null) ? null : identifierArg.longValue()); + wrapped.put("result", output); + } catch (Error | RuntimeException exception) { + wrapped.put("error", wrapError(exception)); + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + } } private static Map wrapError(Throwable exception) { diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/PreviewHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/PreviewHostApiImpl.java new file mode 100644 index 000000000000..838f0b3d656c --- /dev/null +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/PreviewHostApiImpl.java @@ -0,0 +1,149 @@ +// Copyright 2013 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.camerax; + +import android.graphics.SurfaceTexture; +import android.util.Size; +import android.view.Surface; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; +import androidx.camera.core.Preview; +import androidx.camera.core.SurfaceRequest; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugins.camerax.GeneratedCameraXLibrary.PreviewHostApi; +import io.flutter.view.TextureRegistry; +import java.util.Objects; +import java.util.concurrent.Executors; + +public class PreviewHostApiImpl implements PreviewHostApi { + private final BinaryMessenger binaryMessenger; + private final InstanceManager instanceManager; + private final TextureRegistry textureRegistry; + + @VisibleForTesting public CameraXProxy cameraXProxy = new CameraXProxy(); + @VisibleForTesting public TextureRegistry.SurfaceTextureEntry flutterSurfaceTexture; + + public PreviewHostApiImpl( + @NonNull BinaryMessenger binaryMessenger, + @NonNull InstanceManager instanceManager, + @NonNull TextureRegistry textureRegistry) { + this.binaryMessenger = binaryMessenger; + this.instanceManager = instanceManager; + this.textureRegistry = textureRegistry; + } + + /** Creates a {@link Preview} with the target rotation and resolution if specified. */ + @Override + public void create( + @NonNull Long identifier, + @Nullable Long rotation, + @Nullable GeneratedCameraXLibrary.ResolutionInfo targetResolution) { + Preview.Builder previewBuilder = cameraXProxy.createPreviewBuilder(); + if (rotation != null) { + previewBuilder.setTargetRotation(rotation.intValue()); + } + if (targetResolution != null) { + previewBuilder.setTargetResolution( + new Size( + targetResolution.getWidth().intValue(), targetResolution.getHeight().intValue())); + } + Preview preview = previewBuilder.build(); + instanceManager.addDartCreatedInstance(preview, identifier); + } + + /** + * Sets the {@link Preview.SurfaceProvider} that will be used to provide a {@code Surface} backed + * by a Flutter {@link TextureRegistry.SurfaceTextureEntry} used to build the {@link Preview}. + */ + @Override + public Long setSurfaceProvider(@NonNull Long identifier) { + Preview preview = (Preview) Objects.requireNonNull(instanceManager.getInstance(identifier)); + flutterSurfaceTexture = textureRegistry.createSurfaceTexture(); + SurfaceTexture surfaceTexture = flutterSurfaceTexture.surfaceTexture(); + Preview.SurfaceProvider surfaceProvider = createSurfaceProvider(surfaceTexture); + preview.setSurfaceProvider(surfaceProvider); + + return flutterSurfaceTexture.id(); + } + + /** + * Creates a {@link Preview.SurfaceProvider} that specifies how to provide a {@link Surface} to a + * {@code Preview} that is backed by a Flutter {@link TextureRegistry.SurfaceTextureEntry}. + */ + @VisibleForTesting + public Preview.SurfaceProvider createSurfaceProvider(@NonNull SurfaceTexture surfaceTexture) { + return new Preview.SurfaceProvider() { + @Override + public void onSurfaceRequested(SurfaceRequest request) { + surfaceTexture.setDefaultBufferSize( + request.getResolution().getWidth(), request.getResolution().getHeight()); + Surface flutterSurface = cameraXProxy.createSurface(surfaceTexture); + request.provideSurface( + flutterSurface, + Executors.newSingleThreadExecutor(), + (result) -> { + // See https://developer.android.com/reference/androidx/camera/core/SurfaceRequest.Result for documentation. + // Always attempt a release. + flutterSurface.release(); + int resultCode = result.getResultCode(); + switch (resultCode) { + case SurfaceRequest.Result.RESULT_REQUEST_CANCELLED: + case SurfaceRequest.Result.RESULT_WILL_NOT_PROVIDE_SURFACE: + case SurfaceRequest.Result.RESULT_SURFACE_ALREADY_PROVIDED: + case SurfaceRequest.Result.RESULT_SURFACE_USED_SUCCESSFULLY: + // Only need to release, do nothing. + break; + case SurfaceRequest.Result.RESULT_INVALID_SURFACE: // Intentional fall through. + default: + // Release and send error. + SystemServicesFlutterApiImpl systemServicesFlutterApi = + cameraXProxy.createSystemServicesFlutterApiImpl(binaryMessenger); + systemServicesFlutterApi.sendCameraError( + getProvideSurfaceErrorDescription(resultCode), reply -> {}); + break; + } + }); + }; + }; + } + + /** + * Returns an error description for each {@link SurfaceRequest.Result} that represents an error + * with providing a surface. + */ + private String getProvideSurfaceErrorDescription(@Nullable int resultCode) { + switch (resultCode) { + case SurfaceRequest.Result.RESULT_INVALID_SURFACE: + return resultCode + ": Provided surface could not be used by the camera."; + default: + return resultCode + ": Attempt to provide a surface resulted with unrecognizable code."; + } + } + + /** + * Releases the Flutter {@link TextureRegistry.SurfaceTextureEntry} if used to provide a surface + * for a {@link Preview}. + */ + @Override + public void releaseFlutterSurfaceTexture() { + if (flutterSurfaceTexture != null) { + flutterSurfaceTexture.release(); + } + } + + /** Returns the resolution information for the specified {@link Preview}. */ + @Override + public GeneratedCameraXLibrary.ResolutionInfo getResolutionInfo(@NonNull Long identifier) { + Preview preview = (Preview) Objects.requireNonNull(instanceManager.getInstance(identifier)); + Size resolution = preview.getResolutionInfo().getResolution(); + + GeneratedCameraXLibrary.ResolutionInfo.Builder resolutionInfo = + new GeneratedCameraXLibrary.ResolutionInfo.Builder() + .setWidth(Long.valueOf(resolution.getWidth())) + .setHeight(Long.valueOf(resolution.getHeight())); + return resolutionInfo.build(); + } +} diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/SystemServicesFlutterApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/SystemServicesFlutterApiImpl.java index 1e9f33b092bb..63158974f43a 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/SystemServicesFlutterApiImpl.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/SystemServicesFlutterApiImpl.java @@ -4,19 +4,21 @@ package io.flutter.plugins.camerax; +import androidx.annotation.NonNull; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugins.camerax.GeneratedCameraXLibrary.SystemServicesFlutterApi; public class SystemServicesFlutterApiImpl extends SystemServicesFlutterApi { - public SystemServicesFlutterApiImpl( - BinaryMessenger binaryMessenger, InstanceManager instanceManager) { + public SystemServicesFlutterApiImpl(@NonNull BinaryMessenger binaryMessenger) { super(binaryMessenger); - this.instanceManager = instanceManager; } - private final InstanceManager instanceManager; - - public void onDeviceOrientationChanged(String orientation, Reply reply) { + public void sendDeviceOrientationChangedEvent( + @NonNull String orientation, @NonNull Reply reply) { super.onDeviceOrientationChanged(orientation, reply); } + + public void sendCameraError(@NonNull String errorDescription, @NonNull Reply reply) { + super.onCameraError(errorDescription, reply); + } } diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/SystemServicesHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/SystemServicesHostApiImpl.java index e8eb715a7b3a..a6985811531f 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/SystemServicesHostApiImpl.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/SystemServicesHostApiImpl.java @@ -28,8 +28,7 @@ public SystemServicesHostApiImpl( BinaryMessenger binaryMessenger, InstanceManager instanceManager) { this.binaryMessenger = binaryMessenger; this.instanceManager = instanceManager; - this.systemServicesFlutterApi = - new SystemServicesFlutterApiImpl(binaryMessenger, instanceManager); + this.systemServicesFlutterApi = new SystemServicesFlutterApiImpl(binaryMessenger); } public void setActivity(Activity activity) { @@ -86,7 +85,7 @@ public void startListeningForDeviceOrientationChange( isFrontFacing, sensorOrientation.intValue(), (DeviceOrientation newOrientation) -> { - systemServicesFlutterApi.onDeviceOrientationChanged( + systemServicesFlutterApi.sendDeviceOrientationChangedEvent( serializeDeviceOrientation(newOrientation), reply -> {}); }); deviceOrientationManager.start(); diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/PreviewTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/PreviewTest.java new file mode 100644 index 000000000000..9cb4e910dbb8 --- /dev/null +++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/PreviewTest.java @@ -0,0 +1,221 @@ +// Copyright 2013 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.camerax; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.graphics.SurfaceTexture; +import android.util.Size; +import android.view.Surface; +import androidx.camera.core.Preview; +import androidx.camera.core.SurfaceRequest; +import androidx.core.util.Consumer; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugins.camerax.GeneratedCameraXLibrary.ResolutionInfo; +import io.flutter.plugins.camerax.GeneratedCameraXLibrary.SystemServicesFlutterApi.Reply; +import io.flutter.view.TextureRegistry; +import java.util.concurrent.Executor; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.robolectric.RobolectricTestRunner; + +@RunWith(RobolectricTestRunner.class) +public class PreviewTest { + @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); + + @Mock public Preview mockPreview; + @Mock public BinaryMessenger mockBinaryMessenger; + @Mock public TextureRegistry mockTextureRegistry; + @Mock public CameraXProxy mockCameraXProxy; + + InstanceManager testInstanceManager; + + @Before + public void setUp() { + testInstanceManager = spy(InstanceManager.open(identifier -> {})); + } + + @After + public void tearDown() { + testInstanceManager.close(); + } + + @Test + public void create_createsPreviewWithCorrectConfiguration() { + final PreviewHostApiImpl previewHostApi = + new PreviewHostApiImpl(mockBinaryMessenger, testInstanceManager, mockTextureRegistry); + final Preview.Builder mockPreviewBuilder = mock(Preview.Builder.class); + final int targetRotation = 90; + final int targetResolutionWidth = 10; + final int targetResolutionHeight = 50; + final Long previewIdentifier = 3L; + final GeneratedCameraXLibrary.ResolutionInfo resolutionInfo = + new GeneratedCameraXLibrary.ResolutionInfo.Builder() + .setWidth(Long.valueOf(targetResolutionWidth)) + .setHeight(Long.valueOf(targetResolutionHeight)) + .build(); + + previewHostApi.cameraXProxy = mockCameraXProxy; + when(mockCameraXProxy.createPreviewBuilder()).thenReturn(mockPreviewBuilder); + when(mockPreviewBuilder.build()).thenReturn(mockPreview); + + final ArgumentCaptor sizeCaptor = ArgumentCaptor.forClass(Size.class); + + previewHostApi.create(previewIdentifier, Long.valueOf(targetRotation), resolutionInfo); + + verify(mockPreviewBuilder).setTargetRotation(targetRotation); + verify(mockPreviewBuilder).setTargetResolution(sizeCaptor.capture()); + assertEquals(sizeCaptor.getValue().getWidth(), targetResolutionWidth); + assertEquals(sizeCaptor.getValue().getHeight(), targetResolutionHeight); + verify(mockPreviewBuilder).build(); + verify(testInstanceManager).addDartCreatedInstance(mockPreview, previewIdentifier); + } + + @Test + public void setSurfaceProviderTest_createsSurfaceProviderAndReturnsTextureEntryId() { + final PreviewHostApiImpl previewHostApi = + spy(new PreviewHostApiImpl(mockBinaryMessenger, testInstanceManager, mockTextureRegistry)); + final TextureRegistry.SurfaceTextureEntry mockSurfaceTextureEntry = + mock(TextureRegistry.SurfaceTextureEntry.class); + final SurfaceTexture mockSurfaceTexture = mock(SurfaceTexture.class); + final Long previewIdentifier = 5L; + final Long surfaceTextureEntryId = 120L; + + previewHostApi.cameraXProxy = mockCameraXProxy; + testInstanceManager.addDartCreatedInstance(mockPreview, previewIdentifier); + + when(mockTextureRegistry.createSurfaceTexture()).thenReturn(mockSurfaceTextureEntry); + when(mockSurfaceTextureEntry.surfaceTexture()).thenReturn(mockSurfaceTexture); + when(mockSurfaceTextureEntry.id()).thenReturn(surfaceTextureEntryId); + + final ArgumentCaptor surfaceProviderCaptor = + ArgumentCaptor.forClass(Preview.SurfaceProvider.class); + final ArgumentCaptor surfaceCaptor = ArgumentCaptor.forClass(Surface.class); + final ArgumentCaptor consumerCaptor = ArgumentCaptor.forClass(Consumer.class); + + // Test that surface provider was set and the surface texture ID was returned. + assertEquals(previewHostApi.setSurfaceProvider(previewIdentifier), surfaceTextureEntryId); + verify(mockPreview).setSurfaceProvider(surfaceProviderCaptor.capture()); + verify(previewHostApi).createSurfaceProvider(mockSurfaceTexture); + } + + @Test + public void createSurfaceProvider_createsExpectedPreviewSurfaceProvider() { + final PreviewHostApiImpl previewHostApi = + new PreviewHostApiImpl(mockBinaryMessenger, testInstanceManager, mockTextureRegistry); + final SurfaceTexture mockSurfaceTexture = mock(SurfaceTexture.class); + final Surface mockSurface = mock(Surface.class); + final SurfaceRequest mockSurfaceRequest = mock(SurfaceRequest.class); + final SurfaceRequest.Result mockSurfaceRequestResult = mock(SurfaceRequest.Result.class); + final SystemServicesFlutterApiImpl mockSystemServicesFlutterApi = + mock(SystemServicesFlutterApiImpl.class); + final int resolutionWidth = 200; + final int resolutionHeight = 500; + + previewHostApi.cameraXProxy = mockCameraXProxy; + when(mockCameraXProxy.createSurface(mockSurfaceTexture)).thenReturn(mockSurface); + when(mockSurfaceRequest.getResolution()) + .thenReturn(new Size(resolutionWidth, resolutionHeight)); + when(mockCameraXProxy.createSystemServicesFlutterApiImpl(mockBinaryMessenger)) + .thenReturn(mockSystemServicesFlutterApi); + + final ArgumentCaptor surfaceCaptor = ArgumentCaptor.forClass(Surface.class); + final ArgumentCaptor consumerCaptor = ArgumentCaptor.forClass(Consumer.class); + + Preview.SurfaceProvider previewSurfaceProvider = + previewHostApi.createSurfaceProvider(mockSurfaceTexture); + previewSurfaceProvider.onSurfaceRequested(mockSurfaceRequest); + + verify(mockSurfaceTexture).setDefaultBufferSize(resolutionWidth, resolutionHeight); + verify(mockSurfaceRequest) + .provideSurface(surfaceCaptor.capture(), any(Executor.class), consumerCaptor.capture()); + + // Test that the surface derived from the surface texture entry will be provided to the surface request. + assertEquals(surfaceCaptor.getValue(), mockSurface); + + // Test that the Consumer used to handle surface request result releases Flutter surface texture appropriately + // and sends camera errors appropriately. + Consumer capturedConsumer = consumerCaptor.getValue(); + + // Case where Surface should be released. + when(mockSurfaceRequestResult.getResultCode()) + .thenReturn(SurfaceRequest.Result.RESULT_REQUEST_CANCELLED); + capturedConsumer.accept(mockSurfaceRequestResult); + verify(mockSurface).release(); + reset(mockSurface); + + when(mockSurfaceRequestResult.getResultCode()) + .thenReturn(SurfaceRequest.Result.RESULT_REQUEST_CANCELLED); + capturedConsumer.accept(mockSurfaceRequestResult); + verify(mockSurface).release(); + reset(mockSurface); + + when(mockSurfaceRequestResult.getResultCode()) + .thenReturn(SurfaceRequest.Result.RESULT_WILL_NOT_PROVIDE_SURFACE); + capturedConsumer.accept(mockSurfaceRequestResult); + verify(mockSurface).release(); + reset(mockSurface); + + when(mockSurfaceRequestResult.getResultCode()) + .thenReturn(SurfaceRequest.Result.RESULT_SURFACE_USED_SUCCESSFULLY); + capturedConsumer.accept(mockSurfaceRequestResult); + verify(mockSurface).release(); + reset(mockSurface); + + // Case where error must be sent. + when(mockSurfaceRequestResult.getResultCode()) + .thenReturn(SurfaceRequest.Result.RESULT_INVALID_SURFACE); + capturedConsumer.accept(mockSurfaceRequestResult); + verify(mockSurface).release(); + verify(mockSystemServicesFlutterApi).sendCameraError(anyString(), any(Reply.class)); + } + + @Test + public void releaseFlutterSurfaceTexture_makesCallToReleaseFlutterSurfaceTexture() { + final PreviewHostApiImpl previewHostApi = + new PreviewHostApiImpl(mockBinaryMessenger, testInstanceManager, mockTextureRegistry); + final TextureRegistry.SurfaceTextureEntry mockSurfaceTextureEntry = + mock(TextureRegistry.SurfaceTextureEntry.class); + + previewHostApi.flutterSurfaceTexture = mockSurfaceTextureEntry; + + previewHostApi.releaseFlutterSurfaceTexture(); + verify(mockSurfaceTextureEntry).release(); + } + + @Test + public void getResolutionInfo_makesCallToRetrievePreviewResolutionInfo() { + final PreviewHostApiImpl previewHostApi = + new PreviewHostApiImpl(mockBinaryMessenger, testInstanceManager, mockTextureRegistry); + final androidx.camera.core.ResolutionInfo mockResolutionInfo = + mock(androidx.camera.core.ResolutionInfo.class); + final Long previewIdentifier = 23L; + final int resolutionWidth = 500; + final int resolutionHeight = 200; + + testInstanceManager.addDartCreatedInstance(mockPreview, previewIdentifier); + when(mockPreview.getResolutionInfo()).thenReturn(mockResolutionInfo); + when(mockResolutionInfo.getResolution()) + .thenReturn(new Size(resolutionWidth, resolutionHeight)); + + ResolutionInfo resolutionInfo = previewHostApi.getResolutionInfo(previewIdentifier); + assertEquals(resolutionInfo.getWidth(), Long.valueOf(resolutionWidth)); + assertEquals(resolutionInfo.getHeight(), Long.valueOf(resolutionHeight)); + } +} diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/SystemServicesTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/SystemServicesTest.java index d90c2633271c..eb36c452ec3b 100644 --- a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/SystemServicesTest.java +++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/SystemServicesTest.java @@ -129,7 +129,8 @@ public void deviceOrientationChangeTest() { deviceOrientationChangeCallback.onChange(DeviceOrientation.PORTRAIT_DOWN); verify(systemServicesFlutterApi) - .onDeviceOrientationChanged(eq("PORTRAIT_DOWN"), any(Reply.class)); + .sendDeviceOrientationChangedEvent( + eq(DeviceOrientation.PORTRAIT_DOWN.toString()), any(Reply.class)); // Test that the DeviceOrientationManager starts listening for device orientation changes. verify(mockDeviceOrientationManager).start(); diff --git a/packages/camera/camera_android_camerax/lib/src/camerax_library.g.dart b/packages/camera/camera_android_camerax/lib/src/camerax_library.g.dart index 6d8869968f41..1d315e5a1600 100644 --- a/packages/camera/camera_android_camerax/lib/src/camerax_library.g.dart +++ b/packages/camera/camera_android_camerax/lib/src/camerax_library.g.dart @@ -10,6 +10,31 @@ import 'dart:typed_data' show Uint8List, Int32List, Int64List, Float64List; import 'package:flutter/foundation.dart' show WriteBuffer, ReadBuffer; import 'package:flutter/services.dart'; +class ResolutionInfo { + ResolutionInfo({ + required this.width, + required this.height, + }); + + int width; + int height; + + Object encode() { + final Map pigeonMap = {}; + pigeonMap['width'] = width; + pigeonMap['height'] = height; + return pigeonMap; + } + + static ResolutionInfo decode(Object message) { + final Map pigeonMap = message as Map; + return ResolutionInfo( + width: pigeonMap['width']! as int, + height: pigeonMap['height']! as int, + ); + } +} + class CameraPermissionsErrorData { CameraPermissionsErrorData({ required this.errorCode, @@ -634,6 +659,7 @@ abstract class SystemServicesFlutterApi { static const MessageCodec codec = _SystemServicesFlutterApiCodec(); void onDeviceOrientationChanged(String orientation); + void onCameraError(String errorDescription); static void setup(SystemServicesFlutterApi? api, {BinaryMessenger? binaryMessenger}) { { @@ -656,5 +682,174 @@ abstract class SystemServicesFlutterApi { }); } } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.SystemServicesFlutterApi.onCameraError', codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMessageHandler(null); + } else { + channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.SystemServicesFlutterApi.onCameraError was null.'); + final List args = (message as List?)!; + final String? arg_errorDescription = (args[0] as String?); + assert(arg_errorDescription != null, + 'Argument for dev.flutter.pigeon.SystemServicesFlutterApi.onCameraError was null, expected non-null String.'); + api.onCameraError(arg_errorDescription!); + return; + }); + } + } + } +} + +class _PreviewHostApiCodec extends StandardMessageCodec { + const _PreviewHostApiCodec(); + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is ResolutionInfo) { + buffer.putUint8(128); + writeValue(buffer, value.encode()); + } else if (value is ResolutionInfo) { + buffer.putUint8(129); + writeValue(buffer, value.encode()); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + case 128: + return ResolutionInfo.decode(readValue(buffer)!); + + case 129: + return ResolutionInfo.decode(readValue(buffer)!); + + default: + return super.readValueOfType(type, buffer); + } + } +} + +class PreviewHostApi { + /// Constructor for [PreviewHostApi]. The [binaryMessenger] named argument is + /// available for dependency injection. If it is left null, the default + /// BinaryMessenger will be used which routes to the host platform. + PreviewHostApi({BinaryMessenger? binaryMessenger}) + : _binaryMessenger = binaryMessenger; + + final BinaryMessenger? _binaryMessenger; + + static const MessageCodec codec = _PreviewHostApiCodec(); + + Future create(int arg_identifier, int? arg_rotation, + ResolutionInfo? arg_targetResolution) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.PreviewHostApi.create', codec, + binaryMessenger: _binaryMessenger); + final Map? replyMap = await channel + .send([arg_identifier, arg_rotation, arg_targetResolution]) + as Map?; + if (replyMap == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyMap['error'] != null) { + final Map error = + (replyMap['error'] as Map?)!; + throw PlatformException( + code: (error['code'] as String?)!, + message: error['message'] as String?, + details: error['details'], + ); + } else { + return; + } + } + + Future setSurfaceProvider(int arg_identifier) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.PreviewHostApi.setSurfaceProvider', codec, + binaryMessenger: _binaryMessenger); + final Map? replyMap = + await channel.send([arg_identifier]) as Map?; + if (replyMap == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyMap['error'] != null) { + final Map error = + (replyMap['error'] as Map?)!; + throw PlatformException( + code: (error['code'] as String?)!, + message: error['message'] as String?, + details: error['details'], + ); + } else if (replyMap['result'] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (replyMap['result'] as int?)!; + } + } + + Future releaseFlutterSurfaceTexture() async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.PreviewHostApi.releaseFlutterSurfaceTexture', codec, + binaryMessenger: _binaryMessenger); + final Map? replyMap = + await channel.send(null) as Map?; + if (replyMap == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyMap['error'] != null) { + final Map error = + (replyMap['error'] as Map?)!; + throw PlatformException( + code: (error['code'] as String?)!, + message: error['message'] as String?, + details: error['details'], + ); + } else { + return; + } + } + + Future getResolutionInfo(int arg_identifier) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.PreviewHostApi.getResolutionInfo', codec, + binaryMessenger: _binaryMessenger); + final Map? replyMap = + await channel.send([arg_identifier]) as Map?; + if (replyMap == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyMap['error'] != null) { + final Map error = + (replyMap['error'] as Map?)!; + throw PlatformException( + code: (error['code'] as String?)!, + message: error['message'] as String?, + details: error['details'], + ); + } else if (replyMap['result'] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (replyMap['result'] as ResolutionInfo?)!; + } } } diff --git a/packages/camera/camera_android_camerax/lib/src/preview.dart b/packages/camera/camera_android_camerax/lib/src/preview.dart new file mode 100644 index 000000000000..602bcb3da76a --- /dev/null +++ b/packages/camera/camera_android_camerax/lib/src/preview.dart @@ -0,0 +1,126 @@ +// Copyright 2013 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. + +import 'package:flutter/services.dart' show BinaryMessenger; + +import 'camerax_library.g.dart'; +import 'instance_manager.dart'; +import 'java_object.dart'; +import 'use_case.dart'; + +/// Use case that provides a camera preview stream for display. +/// +/// See https://developer.android.com/reference/androidx/camera/core/Preview. +class Preview extends UseCase { + /// Creates a [Preview]. + Preview( + {BinaryMessenger? binaryMessenger, + InstanceManager? instanceManager, + this.targetRotation, + this.targetResolution}) + : super.detached( + binaryMessenger: binaryMessenger, + instanceManager: instanceManager) { + _api = PreviewHostApiImpl( + binaryMessenger: binaryMessenger, instanceManager: instanceManager); + _api.createFromInstance(this, targetRotation, targetResolution); + } + + /// Constructs a [Preview] that is not automatically attached to a native object. + Preview.detached( + {BinaryMessenger? binaryMessenger, + InstanceManager? instanceManager, + this.targetRotation, + this.targetResolution}) + : super.detached( + binaryMessenger: binaryMessenger, + instanceManager: instanceManager) { + _api = PreviewHostApiImpl( + binaryMessenger: binaryMessenger, instanceManager: instanceManager); + } + + late final PreviewHostApiImpl _api; + + /// Target rotation of the camera used for the preview stream. + final int? targetRotation; + + /// Target resolution of the camera preview stream. + final ResolutionInfo? targetResolution; + + /// Sets the surface provider for the preview stream. + /// + /// Returns the ID of the FlutterSurfaceTextureEntry used on the native end + /// used to display the preview stream on a [Texture] of the same ID. + Future setSurfaceProvider() { + return _api.setSurfaceProviderFromInstance(this); + } + + /// Releases Flutter surface texture used to provide a surface for the preview + /// stream. + void releaseFlutterSurfaceTexture() { + _api.releaseFlutterSurfaceTextureFromInstance(); + } + + /// Retrieves the selected resolution information of this [Preview]. + Future getResolutionInfo() { + return _api.getResolutionInfoFromInstance(this); + } +} + +/// Host API implementation of [Preview]. +class PreviewHostApiImpl extends PreviewHostApi { + /// Constructs a [PreviewHostApiImpl]. + PreviewHostApiImpl({this.binaryMessenger, InstanceManager? instanceManager}) { + this.instanceManager = instanceManager ?? JavaObject.globalInstanceManager; + } + + /// Receives binary data across the Flutter platform barrier. + /// + /// If it is null, the default BinaryMessenger will be used which routes to + /// the host platform. + final BinaryMessenger? binaryMessenger; + + /// Maintains instances stored to communicate with native language objects. + late final InstanceManager instanceManager; + + /// Creates a [Preview] with the target rotation provided if specified. + void createFromInstance( + Preview instance, int? targetRotation, ResolutionInfo? targetResolution) { + final int identifier = instanceManager.addDartCreatedInstance(instance, + onCopy: (Preview original) { + return Preview.detached( + binaryMessenger: binaryMessenger, + instanceManager: instanceManager, + targetRotation: original.targetRotation); + }); + create(identifier, targetRotation, targetResolution); + } + + /// Sets the surface provider of the specified [Preview] instance and returns + /// the ID corresponding to the surface it will provide. + Future setSurfaceProviderFromInstance(Preview instance) async { + final int? identifier = instanceManager.getIdentifier(instance); + assert(identifier != null, + 'No Preview has the identifer of that requested to set the surface provider on.'); + + final int surfaceTextureEntryId = await setSurfaceProvider(identifier!); + return surfaceTextureEntryId; + } + + /// Releases Flutter surface texture used to provide a surface for the preview + /// stream if a surface provider was set for a [Preview] instance. + void releaseFlutterSurfaceTextureFromInstance() { + releaseFlutterSurfaceTexture(); + } + + /// Gets the resolution information of the specified [Preview] instance. + Future getResolutionInfoFromInstance(Preview instance) async { + final int? identifier = instanceManager.getIdentifier(instance); + assert(identifier != null, + 'No Preview has the identifer of that requested to get the resolution information for.'); + + final ResolutionInfo resolutionInfo = await getResolutionInfo(identifier!); + return resolutionInfo; + } +} diff --git a/packages/camera/camera_android_camerax/lib/src/surface.dart b/packages/camera/camera_android_camerax/lib/src/surface.dart new file mode 100644 index 000000000000..ea8cf8cb751e --- /dev/null +++ b/packages/camera/camera_android_camerax/lib/src/surface.dart @@ -0,0 +1,34 @@ +// Copyright 2013 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. + +import 'java_object.dart'; + +/// Handle onto the raw buffer managed by screen compositor. +/// +/// See https://developer.android.com/reference/android/view/Surface.html. +class Surface extends JavaObject { + /// Creates a detached [UseCase]. + Surface.detached({super.binaryMessenger, super.instanceManager}) + : super.detached(); + + /// Rotation constant to signify the natural orientation. + /// + /// See https://developer.android.com/reference/android/view/Surface.html#ROTATION_0. + static const int ROTATION_0 = 0; + + /// Rotation constant to signify a 90 degrees rotation. + /// + /// See https://developer.android.com/reference/android/view/Surface.html#ROTATION_90. + static const int ROTATION_90 = 1; + + /// Rotation constant to signify a 180 degrees rotation. + /// + /// See https://developer.android.com/reference/android/view/Surface.html#ROTATION_180. + static const int ROTATION_180 = 2; + + /// Rotation constant to signify a 270 degrees rotation. + /// + /// See https://developer.android.com/reference/android/view/Surface.html#ROTATION_270. + static const int ROTATION_270 = 3; +} diff --git a/packages/camera/camera_android_camerax/lib/src/system_services.dart b/packages/camera/camera_android_camerax/lib/src/system_services.dart index bc6477e0dcb8..4ca90e257a95 100644 --- a/packages/camera/camera_android_camerax/lib/src/system_services.dart +++ b/packages/camera/camera_android_camerax/lib/src/system_services.dart @@ -16,7 +16,7 @@ import 'camerax_library.g.dart'; // ignore_for_file: avoid_classes_with_only_static_members /// Utility class that offers access to Android system services needed for -/// camera usage. +/// camera usage and other informational streams. class SystemServices { /// Stream that emits the device orientation whenever it is changed. /// @@ -26,6 +26,10 @@ class SystemServices { deviceOrientationChangedStreamController = StreamController.broadcast(); + /// Stream that emits the errors caused by camera usage on the native side. + static final StreamController cameraErrorStreamController = + StreamController.broadcast(); + /// Requests permission to access the camera and audio if specified. static Future requestCameraPermissions(bool enableAudio, {BinaryMessenger? binaryMessenger}) { @@ -134,4 +138,12 @@ class SystemServicesFlutterApiImpl implements SystemServicesFlutterApi { '"$orientation" is not a valid DeviceOrientation value'); } } + + /// Callback method for any errors caused by camera usage on the Java side. + @override + void onCameraError(String errorDescription) { + // TODO(camsim99): Use this to implement onCameraError method in plugin. + // See https://github.com/flutter/flutter/issues/119571 for context. + SystemServices.cameraErrorStreamController.add(errorDescription); + } } diff --git a/packages/camera/camera_android_camerax/pigeons/camerax_library.dart b/packages/camera/camera_android_camerax/pigeons/camerax_library.dart index 7fce6ce329fd..4172cd7db073 100644 --- a/packages/camera/camera_android_camerax/pigeons/camerax_library.dart +++ b/packages/camera/camera_android_camerax/pigeons/camerax_library.dart @@ -26,6 +26,16 @@ import 'package:pigeon/pigeon.dart'; ), ), ) +class ResolutionInfo { + ResolutionInfo({ + required this.width, + required this.height, + }); + + int width; + int height; +} + class CameraPermissionsErrorData { CameraPermissionsErrorData({ required this.errorCode, @@ -107,4 +117,17 @@ abstract class SystemServicesHostApi { @FlutterApi() abstract class SystemServicesFlutterApi { void onDeviceOrientationChanged(String orientation); + + void onCameraError(String errorDescription); +} + +@HostApi(dartHostTestHandler: 'TestPreviewHostApi') +abstract class PreviewHostApi { + void create(int identifier, int? rotation, ResolutionInfo? targetResolution); + + int setSurfaceProvider(int identifier); + + void releaseFlutterSurfaceTexture(); + + ResolutionInfo getResolutionInfo(int identifier); } diff --git a/packages/camera/camera_android_camerax/test/preview_test.dart b/packages/camera/camera_android_camerax/test/preview_test.dart new file mode 100644 index 000000000000..36b56f0046e1 --- /dev/null +++ b/packages/camera/camera_android_camerax/test/preview_test.dart @@ -0,0 +1,138 @@ +// Copyright 2013 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. + +import 'package:camera_android_camerax/src/camerax_library.g.dart'; +import 'package:camera_android_camerax/src/instance_manager.dart'; +import 'package:camera_android_camerax/src/preview.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; + +import 'preview_test.mocks.dart'; +import 'test_camerax_library.g.dart'; + +@GenerateMocks([TestPreviewHostApi]) +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('Preview', () { + tearDown(() => TestPreviewHostApi.setup(null)); + + test('detached create does not call create on the Java side', () async { + final MockTestPreviewHostApi mockApi = MockTestPreviewHostApi(); + TestPreviewHostApi.setup(mockApi); + + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: (_) {}, + ); + Preview.detached( + instanceManager: instanceManager, + targetRotation: 90, + targetResolution: ResolutionInfo(width: 50, height: 10), + ); + + verifyNever(mockApi.create(argThat(isA()), argThat(isA()), + argThat(isA()))); + }); + + test('create calls create on the Java side', () async { + final MockTestPreviewHostApi mockApi = MockTestPreviewHostApi(); + TestPreviewHostApi.setup(mockApi); + + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: (_) {}, + ); + const int targetRotation = 90; + const int targetResolutionWidth = 10; + const int targetResolutionHeight = 50; + Preview( + instanceManager: instanceManager, + targetRotation: targetRotation, + targetResolution: ResolutionInfo( + width: targetResolutionWidth, height: targetResolutionHeight), + ); + + final VerificationResult createVerification = verify(mockApi.create( + argThat(isA()), argThat(equals(targetRotation)), captureAny)); + final ResolutionInfo capturedResolutionInfo = + createVerification.captured.single as ResolutionInfo; + expect(capturedResolutionInfo.width, equals(targetResolutionWidth)); + expect(capturedResolutionInfo.height, equals(targetResolutionHeight)); + }); + + test( + 'setSurfaceProvider makes call to set surface provider for preview instance', + () async { + final MockTestPreviewHostApi mockApi = MockTestPreviewHostApi(); + TestPreviewHostApi.setup(mockApi); + + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: (_) {}, + ); + const int textureId = 8; + final Preview preview = Preview.detached( + instanceManager: instanceManager, + ); + instanceManager.addHostCreatedInstance( + preview, + 0, + onCopy: (_) => Preview.detached(), + ); + + when(mockApi.setSurfaceProvider(instanceManager.getIdentifier(preview))) + .thenReturn(textureId); + expect(await preview.setSurfaceProvider(), equals(textureId)); + + verify( + mockApi.setSurfaceProvider(instanceManager.getIdentifier(preview))); + }); + + test( + 'releaseFlutterSurfaceTexture makes call to relase flutter surface texture entry', + () async { + final MockTestPreviewHostApi mockApi = MockTestPreviewHostApi(); + TestPreviewHostApi.setup(mockApi); + + final Preview preview = Preview.detached(); + + preview.releaseFlutterSurfaceTexture(); + + verify(mockApi.releaseFlutterSurfaceTexture()); + }); + + test( + 'getResolutionInfo makes call to get resolution information for preview instance', + () async { + final MockTestPreviewHostApi mockApi = MockTestPreviewHostApi(); + TestPreviewHostApi.setup(mockApi); + + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: (_) {}, + ); + final Preview preview = Preview.detached( + instanceManager: instanceManager, + ); + const int resolutionWidth = 10; + const int resolutionHeight = 60; + final ResolutionInfo testResolutionInfo = + ResolutionInfo(width: resolutionWidth, height: resolutionHeight); + + instanceManager.addHostCreatedInstance( + preview, + 0, + onCopy: (_) => Preview.detached(), + ); + + when(mockApi.getResolutionInfo(instanceManager.getIdentifier(preview))) + .thenReturn(testResolutionInfo); + + final ResolutionInfo previewResolutionInfo = + await preview.getResolutionInfo(); + expect(previewResolutionInfo.width, equals(resolutionWidth)); + expect(previewResolutionInfo.height, equals(resolutionHeight)); + + verify(mockApi.getResolutionInfo(instanceManager.getIdentifier(preview))); + }); + }); +} diff --git a/packages/camera/camera_android_camerax/test/preview_test.mocks.dart b/packages/camera/camera_android_camerax/test/preview_test.mocks.dart new file mode 100644 index 000000000000..60fa1527487b --- /dev/null +++ b/packages/camera/camera_android_camerax/test/preview_test.mocks.dart @@ -0,0 +1,89 @@ +// Mocks generated by Mockito 5.3.2 from annotations +// in camera_android_camerax/test/preview_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'package:camera_android_camerax/src/camerax_library.g.dart' as _i2; +import 'package:mockito/mockito.dart' as _i1; + +import 'test_camerax_library.g.dart' as _i3; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +class _FakeResolutionInfo_0 extends _i1.SmartFake + implements _i2.ResolutionInfo { + _FakeResolutionInfo_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +/// A class which mocks [TestPreviewHostApi]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockTestPreviewHostApi extends _i1.Mock + implements _i3.TestPreviewHostApi { + MockTestPreviewHostApi() { + _i1.throwOnMissingStub(this); + } + + @override + void create( + int? identifier, + int? rotation, + _i2.ResolutionInfo? targetResolution, + ) => + super.noSuchMethod( + Invocation.method( + #create, + [ + identifier, + rotation, + targetResolution, + ], + ), + returnValueForMissingStub: null, + ); + @override + int setSurfaceProvider(int? identifier) => (super.noSuchMethod( + Invocation.method( + #setSurfaceProvider, + [identifier], + ), + returnValue: 0, + ) as int); + @override + void releaseFlutterSurfaceTexture() => super.noSuchMethod( + Invocation.method( + #releaseFlutterSurfaceTexture, + [], + ), + returnValueForMissingStub: null, + ); + @override + _i2.ResolutionInfo getResolutionInfo(int? identifier) => (super.noSuchMethod( + Invocation.method( + #getResolutionInfo, + [identifier], + ), + returnValue: _FakeResolutionInfo_0( + this, + Invocation.method( + #getResolutionInfo, + [identifier], + ), + ), + ) as _i2.ResolutionInfo); +} diff --git a/packages/camera/camera_android_camerax/test/system_services_test.dart b/packages/camera/camera_android_camerax/test/system_services_test.dart index 2d2cea6d9190..38037eaa135c 100644 --- a/packages/camera/camera_android_camerax/test/system_services_test.dart +++ b/packages/camera/camera_android_camerax/test/system_services_test.dart @@ -97,5 +97,14 @@ void main() { 'message', '"FAKE_ORIENTATION" is not a valid DeviceOrientation value'))); }); + + test('onCameraError adds new error to stream', () { + const String testErrorDescription = 'Test error description!'; + SystemServices.cameraErrorStreamController.stream + .listen((String errorDescription) { + expect(errorDescription, equals(testErrorDescription)); + }); + SystemServicesFlutterApiImpl().onCameraError(testErrorDescription); + }); }); } diff --git a/packages/camera/camera_android_camerax/test/test_camerax_library.g.dart b/packages/camera/camera_android_camerax/test/test_camerax_library.g.dart index 55f2c5e3e6a6..3f0e9c2d38a5 100644 --- a/packages/camera/camera_android_camerax/test/test_camerax_library.g.dart +++ b/packages/camera/camera_android_camerax/test/test_camerax_library.g.dart @@ -356,3 +356,120 @@ abstract class TestSystemServicesHostApi { } } } + +class _TestPreviewHostApiCodec extends StandardMessageCodec { + const _TestPreviewHostApiCodec(); + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is ResolutionInfo) { + buffer.putUint8(128); + writeValue(buffer, value.encode()); + } else if (value is ResolutionInfo) { + buffer.putUint8(129); + writeValue(buffer, value.encode()); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + case 128: + return ResolutionInfo.decode(readValue(buffer)!); + + case 129: + return ResolutionInfo.decode(readValue(buffer)!); + + default: + return super.readValueOfType(type, buffer); + } + } +} + +abstract class TestPreviewHostApi { + static const MessageCodec codec = _TestPreviewHostApiCodec(); + + void create(int identifier, int? rotation, ResolutionInfo? targetResolution); + int setSurfaceProvider(int identifier); + void releaseFlutterSurfaceTexture(); + ResolutionInfo getResolutionInfo(int identifier); + static void setup(TestPreviewHostApi? api, + {BinaryMessenger? binaryMessenger}) { + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.PreviewHostApi.create', codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMockMessageHandler(null); + } else { + channel.setMockMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.PreviewHostApi.create was null.'); + final List args = (message as List?)!; + final int? arg_identifier = (args[0] as int?); + assert(arg_identifier != null, + 'Argument for dev.flutter.pigeon.PreviewHostApi.create was null, expected non-null int.'); + final int? arg_rotation = (args[1] as int?); + final ResolutionInfo? arg_targetResolution = + (args[2] as ResolutionInfo?); + api.create(arg_identifier!, arg_rotation, arg_targetResolution); + return {}; + }); + } + } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.PreviewHostApi.setSurfaceProvider', codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMockMessageHandler(null); + } else { + channel.setMockMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.PreviewHostApi.setSurfaceProvider was null.'); + final List args = (message as List?)!; + final int? arg_identifier = (args[0] as int?); + assert(arg_identifier != null, + 'Argument for dev.flutter.pigeon.PreviewHostApi.setSurfaceProvider was null, expected non-null int.'); + final int output = api.setSurfaceProvider(arg_identifier!); + return {'result': output}; + }); + } + } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.PreviewHostApi.releaseFlutterSurfaceTexture', + codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMockMessageHandler(null); + } else { + channel.setMockMessageHandler((Object? message) async { + // ignore message + api.releaseFlutterSurfaceTexture(); + return {}; + }); + } + } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.PreviewHostApi.getResolutionInfo', codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMockMessageHandler(null); + } else { + channel.setMockMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.PreviewHostApi.getResolutionInfo was null.'); + final List args = (message as List?)!; + final int? arg_identifier = (args[0] as int?); + assert(arg_identifier != null, + 'Argument for dev.flutter.pigeon.PreviewHostApi.getResolutionInfo was null, expected non-null int.'); + final ResolutionInfo output = api.getResolutionInfo(arg_identifier!); + return {'result': output}; + }); + } + } + } +} From c9f6bee7177ce532555282c27f4e22ff8017c409 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Feb 2023 19:11:08 +0000 Subject: [PATCH 079/130] [local_auth]: Bump core from 1.8.0 to 1.9.0 in /packages/local_auth/local_auth_android/android (#6393) * [local_auth]: Bump core Bumps core from 1.8.0 to 1.9.0. --- updated-dependencies: - dependency-name: androidx.core:core dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * Bump compilesdkversion * Bump fragment and update changelog * Bump gradle version * Bump compileSdkVersion * Bump all plugins compilesdkversion * Bump gradle version of example apps * Update changelog --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: camsim99 --- packages/local_auth/local_auth/example/android/build.gradle | 2 +- .../android/gradle/wrapper/gradle-wrapper.properties | 2 +- packages/local_auth/local_auth_android/CHANGELOG.md | 4 +++- packages/local_auth/local_auth_android/android/build.gradle | 6 +++--- .../local_auth_android/example/android/app/build.gradle | 2 +- .../local_auth_android/example/android/build.gradle | 2 +- .../android/gradle/wrapper/gradle-wrapper.properties | 2 +- packages/local_auth/local_auth_android/pubspec.yaml | 2 +- script/tool/lib/src/create_all_packages_app_command.dart | 4 ++-- 9 files changed, 14 insertions(+), 12 deletions(-) diff --git a/packages/local_auth/local_auth/example/android/build.gradle b/packages/local_auth/local_auth/example/android/build.gradle index c21bff8e0a2f..3593d9636555 100644 --- a/packages/local_auth/local_auth/example/android/build.gradle +++ b/packages/local_auth/local_auth/example/android/build.gradle @@ -5,7 +5,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:7.0.1' + classpath 'com.android.tools.build:gradle:7.3.1' } } diff --git a/packages/local_auth/local_auth/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/local_auth/local_auth/example/android/gradle/wrapper/gradle-wrapper.properties index 3f383641d7c3..f5c5c374a4b7 100644 --- a/packages/local_auth/local_auth/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/packages/local_auth/local_auth/example/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip diff --git a/packages/local_auth/local_auth_android/CHANGELOG.md b/packages/local_auth/local_auth_android/CHANGELOG.md index 521f656bb8ec..92b671ca119f 100644 --- a/packages/local_auth/local_auth_android/CHANGELOG.md +++ b/packages/local_auth/local_auth_android/CHANGELOG.md @@ -1,6 +1,8 @@ -## NEXT +## 1.0.18 * Updates minimum Flutter version to 3.0. +* Updates androidx.core version to 1.9.0. +* Upgrades compile SDK version to 33. ## 1.0.17 diff --git a/packages/local_auth/local_auth_android/android/build.gradle b/packages/local_auth/local_auth_android/android/build.gradle index 5eecba6278ee..c3ae48a65735 100644 --- a/packages/local_auth/local_auth_android/android/build.gradle +++ b/packages/local_auth/local_auth_android/android/build.gradle @@ -8,7 +8,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:7.2.1' + classpath 'com.android.tools.build:gradle:7.3.1' } } @@ -22,7 +22,7 @@ rootProject.allprojects { apply plugin: 'com.android.library' android { - compileSdkVersion 31 + compileSdkVersion 33 defaultConfig { minSdkVersion 16 @@ -51,7 +51,7 @@ android { } dependencies { - api "androidx.core:core:1.8.0" + api "androidx.core:core:1.9.0" api "androidx.biometric:biometric:1.1.0" api "androidx.fragment:fragment:1.5.5" testImplementation 'junit:junit:4.13.2' diff --git a/packages/local_auth/local_auth_android/example/android/app/build.gradle b/packages/local_auth/local_auth_android/example/android/app/build.gradle index 3c6eca7ce8a7..0146852feb44 100644 --- a/packages/local_auth/local_auth_android/example/android/app/build.gradle +++ b/packages/local_auth/local_auth_android/example/android/app/build.gradle @@ -25,7 +25,7 @@ apply plugin: 'com.android.application' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { - compileSdkVersion 31 + compileSdkVersion 33 lintOptions { disable 'InvalidPackage' diff --git a/packages/local_auth/local_auth_android/example/android/build.gradle b/packages/local_auth/local_auth_android/example/android/build.gradle index c21bff8e0a2f..3593d9636555 100644 --- a/packages/local_auth/local_auth_android/example/android/build.gradle +++ b/packages/local_auth/local_auth_android/example/android/build.gradle @@ -5,7 +5,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:7.0.1' + classpath 'com.android.tools.build:gradle:7.3.1' } } diff --git a/packages/local_auth/local_auth_android/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/local_auth/local_auth_android/example/android/gradle/wrapper/gradle-wrapper.properties index 3f383641d7c3..f5c5c374a4b7 100644 --- a/packages/local_auth/local_auth_android/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/packages/local_auth/local_auth_android/example/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip diff --git a/packages/local_auth/local_auth_android/pubspec.yaml b/packages/local_auth/local_auth_android/pubspec.yaml index aa05acb8a88c..bc81476565cb 100644 --- a/packages/local_auth/local_auth_android/pubspec.yaml +++ b/packages/local_auth/local_auth_android/pubspec.yaml @@ -2,7 +2,7 @@ name: local_auth_android description: Android implementation of the local_auth plugin. repository: https://github.com/flutter/plugins/tree/main/packages/local_auth/local_auth_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+local_auth%22 -version: 1.0.17 +version: 1.0.18 environment: sdk: ">=2.14.0 <3.0.0" diff --git a/script/tool/lib/src/create_all_packages_app_command.dart b/script/tool/lib/src/create_all_packages_app_command.dart index e7719e9f664c..b56086ac1d0a 100644 --- a/script/tool/lib/src/create_all_packages_app_command.dart +++ b/script/tool/lib/src/create_all_packages_app_command.dart @@ -130,8 +130,8 @@ class CreateAllPackagesAppCommand extends PackageCommand { // minSdkVersion 19 is required by WebView. newGradle.writeln('minSdkVersion 20'); } else if (line.contains('compileSdkVersion')) { - // compileSdkVersion 32 is required by webview_flutter. - newGradle.writeln('compileSdkVersion 32'); + // compileSdkVersion 33 is required by local_auth. + newGradle.writeln('compileSdkVersion 33'); } else { newGradle.writeln(line); } From f5957bde8f5df964f26bbf6f9b706e19768a52b4 Mon Sep 17 00:00:00 2001 From: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> Date: Mon, 6 Feb 2023 15:21:05 -0500 Subject: [PATCH 080/130] [webview_flutter_web] Avoids XHR when possible. (#7090) * update * Request body must be empty too to skip XHR request. Add test. * Add ContentType class to parse response headers. * Use content-type in response to encode iframe contents. * Attempt to run integration_tests. Do they ever fail? * Update docs. * Set Widget styles in a way the flutter engine likes. * Add bash to codeblocks in readme. --------- Co-authored-by: David Iglesias Teixeira --- .../webview_flutter_web/CHANGELOG.md | 11 ++- .../webview_flutter_web/README.md | 16 ++++ .../webview_flutter_test.dart | 4 + .../webview_flutter_web/example/run_test.sh | 22 ++++++ .../lib/src/content_type.dart | 48 ++++++++++++ .../lib/src/web_webview_controller.dart | 34 ++++++-- .../webview_flutter_web/pubspec.yaml | 2 +- .../test/content_type_test.dart | 77 ++++++++++++++++++ .../test/web_webview_controller_test.dart | 78 ++++++++++++++++--- .../web_webview_controller_test.mocks.dart | 58 ++++++++++---- 10 files changed, 317 insertions(+), 33 deletions(-) create mode 100755 packages/webview_flutter/webview_flutter_web/example/run_test.sh create mode 100644 packages/webview_flutter/webview_flutter_web/lib/src/content_type.dart create mode 100644 packages/webview_flutter/webview_flutter_web/test/content_type_test.dart diff --git a/packages/webview_flutter/webview_flutter_web/CHANGELOG.md b/packages/webview_flutter/webview_flutter_web/CHANGELOG.md index 028e03d715ff..3ada124fe7ce 100644 --- a/packages/webview_flutter/webview_flutter_web/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter_web/CHANGELOG.md @@ -1,5 +1,12 @@ -## NEXT - +## 0.2.2 + +* Updates `WebWebViewController.loadRequest` to only set the src of the iFrame + when `LoadRequestParams.headers` and `LoadRequestParams.body` are empty and is + using the HTTP GET request method. [#118573](https://github.com/flutter/flutter/issues/118573). +* Parses the `content-type` header of XHR responses to extract the correct + MIME-type and charset. [#118090](https://github.com/flutter/flutter/issues/118090). +* Sets `width` and `height` of widget the way the Engine wants, to remove distracting + warnings from the development console. * Updates minimum Flutter version to 3.0. ## 0.2.1 diff --git a/packages/webview_flutter/webview_flutter_web/README.md b/packages/webview_flutter/webview_flutter_web/README.md index 51a0223696d0..03bb6a89052e 100644 --- a/packages/webview_flutter/webview_flutter_web/README.md +++ b/packages/webview_flutter/webview_flutter_web/README.md @@ -21,3 +21,19 @@ yet, so it currently requires extra setup to use: Once the step above is complete, the APIs from `webview_flutter` listed above can be used as normal on web. + +## Tests + +Tests are contained in the `test` directory. You can run all tests from the root +of the package with the following command: + +```bash +$ flutter test --platform chrome +``` + +This package uses `package:mockito` in some tests. Mock files can be updated +from the root of the package like so: + +```bash +$ flutter pub run build_runner build --delete-conflicting-outputs +``` diff --git a/packages/webview_flutter/webview_flutter_web/example/integration_test/webview_flutter_test.dart b/packages/webview_flutter/webview_flutter_web/example/integration_test/webview_flutter_test.dart index 1736d47d39c8..f71d2d3c2bac 100644 --- a/packages/webview_flutter/webview_flutter_web/example/integration_test/webview_flutter_test.dart +++ b/packages/webview_flutter/webview_flutter_web/example/integration_test/webview_flutter_test.dart @@ -5,6 +5,10 @@ import 'dart:html' as html; import 'dart:io'; +// FIX (dit): Remove these integration tests, or make them run. They currently never fail. +// (They won't run because they use `dart:io`. If you remove all `dart:io` bits from +// this file, they start failing with `fail()`, for example.) + import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; diff --git a/packages/webview_flutter/webview_flutter_web/example/run_test.sh b/packages/webview_flutter/webview_flutter_web/example/run_test.sh new file mode 100755 index 000000000000..aa52974f310e --- /dev/null +++ b/packages/webview_flutter/webview_flutter_web/example/run_test.sh @@ -0,0 +1,22 @@ +#!/usr/bin/bash +# Copyright 2013 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. + +if pgrep -lf chromedriver > /dev/null; then + echo "chromedriver is running." + + if [ $# -eq 0 ]; then + echo "No target specified, running all tests..." + find integration_test/ -iname *_test.dart | xargs -n1 -i -t flutter drive -d web-server --web-port=7357 --browser-name=chrome --driver=test_driver/integration_test.dart --target='{}' + else + echo "Running test target: $1..." + set -x + flutter drive -d web-server --web-port=7357 --browser-name=chrome --driver=test_driver/integration_test.dart --target=$1 + fi + + else + echo "chromedriver is not running." + echo "Please, check the README.md for instructions on how to use run_test.sh" +fi + diff --git a/packages/webview_flutter/webview_flutter_web/lib/src/content_type.dart b/packages/webview_flutter/webview_flutter_web/lib/src/content_type.dart new file mode 100644 index 000000000000..0aa18ce2318a --- /dev/null +++ b/packages/webview_flutter/webview_flutter_web/lib/src/content_type.dart @@ -0,0 +1,48 @@ +// Copyright 2013 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. + +/// Class to represent a content-type header value. +class ContentType { + /// Creates a [ContentType] instance by parsing a "content-type" response [header]. + /// + /// See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type + /// See: https://httpwg.org/specs/rfc9110.html#media.type + ContentType.parse(String header) { + final Iterable chunks = + header.split(';').map((String e) => e.trim().toLowerCase()); + + for (final String chunk in chunks) { + if (!chunk.contains('=')) { + _mimeType = chunk; + } else { + final List bits = + chunk.split('=').map((String e) => e.trim()).toList(); + assert(bits.length == 2); + switch (bits[0]) { + case 'charset': + _charset = bits[1]; + break; + case 'boundary': + _boundary = bits[1]; + break; + default: + throw StateError('Unable to parse "$chunk" in content-type.'); + } + } + } + } + + String? _mimeType; + String? _charset; + String? _boundary; + + /// The MIME-type of the resource or the data. + String? get mimeType => _mimeType; + + /// The character encoding standard. + String? get charset => _charset; + + /// The separation boundary for multipart entities. + String? get boundary => _boundary; +} diff --git a/packages/webview_flutter/webview_flutter_web/lib/src/web_webview_controller.dart b/packages/webview_flutter/webview_flutter_web/lib/src/web_webview_controller.dart index 7ef72257999f..52f93f911e40 100644 --- a/packages/webview_flutter/webview_flutter_web/lib/src/web_webview_controller.dart +++ b/packages/webview_flutter/webview_flutter_web/lib/src/web_webview_controller.dart @@ -3,11 +3,12 @@ // found in the LICENSE file. import 'dart:convert'; -import 'dart:html'; +import 'dart:html' as html; import 'package:flutter/cupertino.dart'; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; +import 'content_type.dart'; import 'http_request_factory.dart'; import 'shims/dart_ui.dart' as ui; @@ -37,10 +38,10 @@ class WebWebViewControllerCreationParams /// The underlying element used as the WebView. @visibleForTesting - final IFrameElement iFrame = IFrameElement() + final html.IFrameElement iFrame = html.IFrameElement() ..id = 'webView${_nextIFrameId++}' - ..width = '100%' - ..height = '100%' + ..style.width = '100%' + ..style.height = '100%' ..style.border = 'none'; } @@ -72,20 +73,37 @@ class WebWebViewController extends PlatformWebViewController { throw ArgumentError( 'LoadRequestParams#uri is required to have a scheme.'); } - final HttpRequest httpReq = + + if (params.headers.isEmpty && + (params.body == null || params.body!.isEmpty) && + params.method == LoadRequestMethod.get) { + // ignore: unsafe_html + _webWebViewParams.iFrame.src = params.uri.toString(); + } else { + await _updateIFrameFromXhr(params); + } + } + + /// Performs an AJAX request defined by [params]. + Future _updateIFrameFromXhr(LoadRequestParams params) async { + final html.HttpRequest httpReq = await _webWebViewParams.httpRequestFactory.request( params.uri.toString(), method: params.method.serialize(), requestHeaders: params.headers, sendData: params.body, ); - final String contentType = + + final String header = httpReq.getResponseHeader('content-type') ?? 'text/html'; + final ContentType contentType = ContentType.parse(header); + final Encoding encoding = Encoding.getByName(contentType.charset) ?? utf8; + // ignore: unsafe_html _webWebViewParams.iFrame.src = Uri.dataFromString( httpReq.responseText ?? '', - mimeType: contentType, - encoding: utf8, + mimeType: contentType.mimeType, + encoding: encoding, ).toString(); } } diff --git a/packages/webview_flutter/webview_flutter_web/pubspec.yaml b/packages/webview_flutter/webview_flutter_web/pubspec.yaml index 66b67f4d67bd..f3ea67d68dad 100644 --- a/packages/webview_flutter/webview_flutter_web/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_web/pubspec.yaml @@ -2,7 +2,7 @@ name: webview_flutter_web description: A Flutter plugin that provides a WebView widget on web. repository: https://github.com/flutter/plugins/tree/main/packages/webview_flutter/webview_flutter_web issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22 -version: 0.2.1 +version: 0.2.2 environment: sdk: ">=2.14.0 <3.0.0" diff --git a/packages/webview_flutter/webview_flutter_web/test/content_type_test.dart b/packages/webview_flutter/webview_flutter_web/test/content_type_test.dart new file mode 100644 index 000000000000..936eeae4f571 --- /dev/null +++ b/packages/webview_flutter/webview_flutter_web/test/content_type_test.dart @@ -0,0 +1,77 @@ +// Copyright 2013 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. + +import 'package:flutter_test/flutter_test.dart'; +import 'package:webview_flutter_web/src/content_type.dart'; + +void main() { + group('ContentType.parse', () { + test('basic content-type (lowers case)', () { + final ContentType contentType = ContentType.parse('text/pLaIn'); + + expect(contentType.mimeType, 'text/plain'); + expect(contentType.boundary, isNull); + expect(contentType.charset, isNull); + }); + + test('with charset', () { + final ContentType contentType = + ContentType.parse('text/pLaIn; charset=utf-8'); + + expect(contentType.mimeType, 'text/plain'); + expect(contentType.boundary, isNull); + expect(contentType.charset, 'utf-8'); + }); + + test('with boundary', () { + final ContentType contentType = + ContentType.parse('text/pLaIn; boundary=---xyz'); + + expect(contentType.mimeType, 'text/plain'); + expect(contentType.boundary, '---xyz'); + expect(contentType.charset, isNull); + }); + + test('with charset and boundary', () { + final ContentType contentType = + ContentType.parse('text/pLaIn; charset=utf-8; boundary=---xyz'); + + expect(contentType.mimeType, 'text/plain'); + expect(contentType.boundary, '---xyz'); + expect(contentType.charset, 'utf-8'); + }); + + test('with boundary and charset', () { + final ContentType contentType = + ContentType.parse('text/pLaIn; boundary=---xyz; charset=utf-8'); + + expect(contentType.mimeType, 'text/plain'); + expect(contentType.boundary, '---xyz'); + expect(contentType.charset, 'utf-8'); + }); + + test('with a bunch of whitespace, boundary and charset', () { + final ContentType contentType = ContentType.parse( + ' text/pLaIn ; boundary=---xyz; charset=utf-8 '); + + expect(contentType.mimeType, 'text/plain'); + expect(contentType.boundary, '---xyz'); + expect(contentType.charset, 'utf-8'); + }); + + test('empty string', () { + final ContentType contentType = ContentType.parse(''); + + expect(contentType.mimeType, ''); + expect(contentType.boundary, isNull); + expect(contentType.charset, isNull); + }); + + test('unknown parameter (throws)', () { + expect(() { + ContentType.parse('text/pLaIn; wrong=utf-8'); + }, throwsStateError); + }); + }); +} diff --git a/packages/webview_flutter/webview_flutter_web/test/web_webview_controller_test.dart b/packages/webview_flutter/webview_flutter_web/test/web_webview_controller_test.dart index 6a8f73798107..0a995cbb67e0 100644 --- a/packages/webview_flutter/webview_flutter_web/test/web_webview_controller_test.dart +++ b/packages/webview_flutter/webview_flutter_web/test/web_webview_controller_test.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:convert'; import 'dart:html'; // TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#104231) // ignore: unnecessary_import @@ -17,9 +18,9 @@ import 'package:webview_flutter_web/webview_flutter_web.dart'; import 'web_webview_controller_test.mocks.dart'; -@GenerateMocks([ - HttpRequest, - HttpRequestFactory, +@GenerateMocks([], customMocks: >[ + MockSpec(onMissingStub: OnMissingStub.returnDefault), + MockSpec(onMissingStub: OnMissingStub.returnDefault), ]) void main() { WidgetsFlutterBinding.ensureInitialized(); @@ -31,8 +32,8 @@ void main() { WebWebViewControllerCreationParams(); expect(params.iFrame.id, contains('webView')); - expect(params.iFrame.width, '100%'); - expect(params.iFrame.height, '100%'); + expect(params.iFrame.style.width, '100%'); + expect(params.iFrame.style.height, '100%'); expect(params.iFrame.style.border, 'none'); }); }); @@ -62,7 +63,7 @@ void main() { }); group('loadRequest', () { - test('loadRequest throws ArgumentError on missing scheme', () async { + test('throws ArgumentError on missing scheme', () async { final WebWebViewController controller = WebWebViewController(WebWebViewControllerCreationParams()); @@ -73,8 +74,33 @@ void main() { throwsA(const TypeMatcher())); }); - test('loadRequest makes request and loads response into iframe', - () async { + test('skips XHR for simple GETs (no headers, no data)', () async { + final MockHttpRequestFactory mockHttpRequestFactory = + MockHttpRequestFactory(); + final WebWebViewController controller = + WebWebViewController(WebWebViewControllerCreationParams( + httpRequestFactory: mockHttpRequestFactory, + )); + + when(mockHttpRequestFactory.request( + any, + method: anyNamed('method'), + requestHeaders: anyNamed('requestHeaders'), + sendData: anyNamed('sendData'), + )).thenThrow( + StateError('The `request` method should not have been called.')); + + await controller.loadRequest(LoadRequestParams( + uri: Uri.parse('https://flutter.dev'), + )); + + expect( + (controller.params as WebWebViewControllerCreationParams).iFrame.src, + 'https://flutter.dev/', + ); + }); + + test('makes request and loads response into iframe', () async { final MockHttpRequestFactory mockHttpRequestFactory = MockHttpRequestFactory(); final WebWebViewController controller = @@ -114,7 +140,41 @@ void main() { ); }); - test('loadRequest escapes "#" correctly', () async { + test('parses content-type response header correctly', () async { + final MockHttpRequestFactory mockHttpRequestFactory = + MockHttpRequestFactory(); + final WebWebViewController controller = + WebWebViewController(WebWebViewControllerCreationParams( + httpRequestFactory: mockHttpRequestFactory, + )); + + final Encoding iso = Encoding.getByName('latin1')!; + + final MockHttpRequest mockHttpRequest = MockHttpRequest(); + when(mockHttpRequest.responseText) + .thenReturn(String.fromCharCodes(iso.encode('España'))); + when(mockHttpRequest.getResponseHeader('content-type')) + .thenReturn('Text/HTmL; charset=latin1'); + + when(mockHttpRequestFactory.request( + any, + method: anyNamed('method'), + requestHeaders: anyNamed('requestHeaders'), + sendData: anyNamed('sendData'), + )).thenAnswer((_) => Future.value(mockHttpRequest)); + + await controller.loadRequest(LoadRequestParams( + uri: Uri.parse('https://flutter.dev'), + method: LoadRequestMethod.post, + )); + + expect( + (controller.params as WebWebViewControllerCreationParams).iFrame.src, + 'data:text/html;charset=iso-8859-1,Espa%F1a', + ); + }); + + test('escapes "#" correctly', () async { final MockHttpRequestFactory mockHttpRequestFactory = MockHttpRequestFactory(); final WebWebViewController controller = diff --git a/packages/webview_flutter/webview_flutter_web/test/web_webview_controller_test.mocks.dart b/packages/webview_flutter/webview_flutter_web/test/web_webview_controller_test.mocks.dart index f74359aac431..5cb259a3f01a 100644 --- a/packages/webview_flutter/webview_flutter_web/test/web_webview_controller_test.mocks.dart +++ b/packages/webview_flutter/webview_flutter_web/test/web_webview_controller_test.mocks.dart @@ -55,24 +55,23 @@ class _FakeHttpRequest_2 extends _i1.SmartFake implements _i2.HttpRequest { /// /// See the documentation for Mockito's code generation for more information. class MockHttpRequest extends _i1.Mock implements _i2.HttpRequest { - MockHttpRequest() { - _i1.throwOnMissingStub(this); - } - @override Map get responseHeaders => (super.noSuchMethod( Invocation.getter(#responseHeaders), returnValue: {}, + returnValueForMissingStub: {}, ) as Map); @override int get readyState => (super.noSuchMethod( Invocation.getter(#readyState), returnValue: 0, + returnValueForMissingStub: 0, ) as int); @override String get responseType => (super.noSuchMethod( Invocation.getter(#responseType), returnValue: '', + returnValueForMissingStub: '', ) as String); @override set responseType(String? value) => super.noSuchMethod( @@ -97,6 +96,10 @@ class MockHttpRequest extends _i1.Mock implements _i2.HttpRequest { this, Invocation.getter(#upload), ), + returnValueForMissingStub: _FakeHttpRequestUpload_0( + this, + Invocation.getter(#upload), + ), ) as _i2.HttpRequestUpload); @override set withCredentials(bool? value) => super.noSuchMethod( @@ -110,41 +113,49 @@ class MockHttpRequest extends _i1.Mock implements _i2.HttpRequest { _i3.Stream<_i2.Event> get onReadyStateChange => (super.noSuchMethod( Invocation.getter(#onReadyStateChange), returnValue: _i3.Stream<_i2.Event>.empty(), + returnValueForMissingStub: _i3.Stream<_i2.Event>.empty(), ) as _i3.Stream<_i2.Event>); @override _i3.Stream<_i2.ProgressEvent> get onAbort => (super.noSuchMethod( Invocation.getter(#onAbort), returnValue: _i3.Stream<_i2.ProgressEvent>.empty(), + returnValueForMissingStub: _i3.Stream<_i2.ProgressEvent>.empty(), ) as _i3.Stream<_i2.ProgressEvent>); @override _i3.Stream<_i2.ProgressEvent> get onError => (super.noSuchMethod( Invocation.getter(#onError), returnValue: _i3.Stream<_i2.ProgressEvent>.empty(), + returnValueForMissingStub: _i3.Stream<_i2.ProgressEvent>.empty(), ) as _i3.Stream<_i2.ProgressEvent>); @override _i3.Stream<_i2.ProgressEvent> get onLoad => (super.noSuchMethod( Invocation.getter(#onLoad), returnValue: _i3.Stream<_i2.ProgressEvent>.empty(), + returnValueForMissingStub: _i3.Stream<_i2.ProgressEvent>.empty(), ) as _i3.Stream<_i2.ProgressEvent>); @override _i3.Stream<_i2.ProgressEvent> get onLoadEnd => (super.noSuchMethod( Invocation.getter(#onLoadEnd), returnValue: _i3.Stream<_i2.ProgressEvent>.empty(), + returnValueForMissingStub: _i3.Stream<_i2.ProgressEvent>.empty(), ) as _i3.Stream<_i2.ProgressEvent>); @override _i3.Stream<_i2.ProgressEvent> get onLoadStart => (super.noSuchMethod( Invocation.getter(#onLoadStart), returnValue: _i3.Stream<_i2.ProgressEvent>.empty(), + returnValueForMissingStub: _i3.Stream<_i2.ProgressEvent>.empty(), ) as _i3.Stream<_i2.ProgressEvent>); @override _i3.Stream<_i2.ProgressEvent> get onProgress => (super.noSuchMethod( Invocation.getter(#onProgress), returnValue: _i3.Stream<_i2.ProgressEvent>.empty(), + returnValueForMissingStub: _i3.Stream<_i2.ProgressEvent>.empty(), ) as _i3.Stream<_i2.ProgressEvent>); @override _i3.Stream<_i2.ProgressEvent> get onTimeout => (super.noSuchMethod( Invocation.getter(#onTimeout), returnValue: _i3.Stream<_i2.ProgressEvent>.empty(), + returnValueForMissingStub: _i3.Stream<_i2.ProgressEvent>.empty(), ) as _i3.Stream<_i2.ProgressEvent>); @override _i2.Events get on => (super.noSuchMethod( @@ -153,6 +164,10 @@ class MockHttpRequest extends _i1.Mock implements _i2.HttpRequest { this, Invocation.getter(#on), ), + returnValueForMissingStub: _FakeEvents_1( + this, + Invocation.getter(#on), + ), ) as _i2.Events); @override void open( @@ -192,13 +207,16 @@ class MockHttpRequest extends _i1.Mock implements _i2.HttpRequest { [], ), returnValue: '', + returnValueForMissingStub: '', ) as String); @override - String? getResponseHeader(String? name) => - (super.noSuchMethod(Invocation.method( - #getResponseHeader, - [name], - )) as String?); + String? getResponseHeader(String? name) => (super.noSuchMethod( + Invocation.method( + #getResponseHeader, + [name], + ), + returnValueForMissingStub: null, + ) as String?); @override void overrideMimeType(String? mime) => super.noSuchMethod( Invocation.method( @@ -271,6 +289,7 @@ class MockHttpRequest extends _i1.Mock implements _i2.HttpRequest { [event], ), returnValue: false, + returnValueForMissingStub: false, ) as bool); } @@ -279,10 +298,6 @@ class MockHttpRequest extends _i1.Mock implements _i2.HttpRequest { /// See the documentation for Mockito's code generation for more information. class MockHttpRequestFactory extends _i1.Mock implements _i4.HttpRequestFactory { - MockHttpRequestFactory() { - _i1.throwOnMissingStub(this); - } - @override _i3.Future<_i2.HttpRequest> request( String? url, { @@ -324,5 +339,22 @@ class MockHttpRequestFactory extends _i1.Mock }, ), )), + returnValueForMissingStub: + _i3.Future<_i2.HttpRequest>.value(_FakeHttpRequest_2( + this, + Invocation.method( + #request, + [url], + { + #method: method, + #withCredentials: withCredentials, + #responseType: responseType, + #mimeType: mimeType, + #requestHeaders: requestHeaders, + #sendData: sendData, + #onProgress: onProgress, + }, + ), + )), ) as _i3.Future<_i2.HttpRequest>); } From 55f2573821d60adfec2e3f4c26e84eeab96fa705 Mon Sep 17 00:00:00 2001 From: Reid Baker Date: Mon, 6 Feb 2023 16:27:34 -0500 Subject: [PATCH 081/130] [Espresso] Update expressio dependencies (#7108) * Update gradle and gson dependencies * Update changelog and version * Modify tense to follow changelog style --- packages/espresso/CHANGELOG.md | 3 ++- packages/espresso/android/build.gradle | 4 ++-- packages/espresso/pubspec.yaml | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/espresso/CHANGELOG.md b/packages/espresso/CHANGELOG.md index 709bd1925bbf..715a3bca5786 100644 --- a/packages/espresso/CHANGELOG.md +++ b/packages/espresso/CHANGELOG.md @@ -1,5 +1,6 @@ -## NEXT +## 0.2.0+7 +* Updates espresso gradle and gson dependencies. * Updates minimum Flutter version to 3.0. ## 0.2.0+6 diff --git a/packages/espresso/android/build.gradle b/packages/espresso/android/build.gradle index 1444eb9f8a85..47a81b6eea27 100644 --- a/packages/espresso/android/build.gradle +++ b/packages/espresso/android/build.gradle @@ -8,7 +8,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:7.3.1' + classpath 'com.android.tools.build:gradle:7.4.1' } } @@ -53,7 +53,7 @@ android { dependencies { implementation 'com.google.guava:guava:31.1-android' implementation 'com.squareup.okhttp3:okhttp:4.10.0' - implementation 'com.google.code.gson:gson:2.9.1' + implementation 'com.google.code.gson:gson:2.10.1' androidTestImplementation 'org.hamcrest:hamcrest:2.2' testImplementation 'junit:junit:4.13.2' diff --git a/packages/espresso/pubspec.yaml b/packages/espresso/pubspec.yaml index be018c9f8274..350e3a681db7 100644 --- a/packages/espresso/pubspec.yaml +++ b/packages/espresso/pubspec.yaml @@ -3,7 +3,7 @@ description: Java classes for testing Flutter apps using Espresso. Allows driving Flutter widgets from a native Espresso test. repository: https://github.com/flutter/plugins/tree/main/packages/espresso issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+espresso%22 -version: 0.2.0+6 +version: 0.2.0+7 environment: sdk: ">=2.12.0 <3.0.0" From 76ea8c8ce08bed07df467ade34be457aa2531e0a Mon Sep 17 00:00:00 2001 From: Jenn Magder Date: Mon, 6 Feb 2023 15:43:26 -0800 Subject: [PATCH 082/130] [url_launcher_ios] Update minimum Flutter version to 3.3 and iOS 11 (#7110) * [url_launcher_ios] Update minimum Flutter version to 3.3 and iOS 11 * super --- .../url_launcher/url_launcher/CHANGELOG.md | 3 +- packages/url_launcher/url_launcher/README.md | 6 ++-- .../ios/Flutter/AppFrameworkInfo.plist | 2 +- .../url_launcher/example/ios/Podfile | 2 +- .../ios/Runner.xcodeproj/project.pbxproj | 11 ++++--- .../xcshareddata/xcschemes/Runner.xcscheme | 2 +- .../example/ios/Runner/Info.plist | 4 +++ .../url_launcher/url_launcher/pubspec.yaml | 2 +- .../url_launcher_ios/CHANGELOG.md | 4 +-- .../ios/Flutter/AppFrameworkInfo.plist | 2 +- .../url_launcher_ios/example/ios/Podfile | 2 +- .../ios/Runner.xcodeproj/project.pbxproj | 11 ++++--- .../xcshareddata/xcschemes/Runner.xcscheme | 2 +- .../example/ios/Runner/Info.plist | 4 +++ .../url_launcher_ios/example/lib/main.dart | 4 +-- .../url_launcher_ios/example/pubspec.yaml | 4 +-- .../ios/Classes/FLTURLLauncherPlugin.m | 32 +++++++------------ .../ios/url_launcher_ios.podspec | 3 +- .../url_launcher_ios/pubspec.yaml | 6 ++-- 19 files changed, 55 insertions(+), 51 deletions(-) diff --git a/packages/url_launcher/url_launcher/CHANGELOG.md b/packages/url_launcher/url_launcher/CHANGELOG.md index dbb33aa34bba..4079520d9120 100644 --- a/packages/url_launcher/url_launcher/CHANGELOG.md +++ b/packages/url_launcher/url_launcher/CHANGELOG.md @@ -1,6 +1,7 @@ -## NEXT +## 6.1.9 * Updates minimum Flutter version to 3.0. +* Updates iOS minimum version in README. ## 6.1.8 diff --git a/packages/url_launcher/url_launcher/README.md b/packages/url_launcher/url_launcher/README.md index 8fbdfb3930e1..b394e4ad6395 100644 --- a/packages/url_launcher/url_launcher/README.md +++ b/packages/url_launcher/url_launcher/README.md @@ -6,9 +6,9 @@ A Flutter plugin for launching a URL. -| | Android | iOS | Linux | macOS | Web | Windows | -|-------------|---------|------|-------|--------|-----|-------------| -| **Support** | SDK 16+ | 9.0+ | Any | 10.11+ | Any | Windows 10+ | +| | Android | iOS | Linux | macOS | Web | Windows | +|-------------|---------|-------|-------|--------|-----|-------------| +| **Support** | SDK 16+ | 11.0+ | Any | 10.11+ | Any | Windows 10+ | ## Usage diff --git a/packages/url_launcher/url_launcher/example/ios/Flutter/AppFrameworkInfo.plist b/packages/url_launcher/url_launcher/example/ios/Flutter/AppFrameworkInfo.plist index 3a9c234f96d4..9b41e7d87980 100644 --- a/packages/url_launcher/url_launcher/example/ios/Flutter/AppFrameworkInfo.plist +++ b/packages/url_launcher/url_launcher/example/ios/Flutter/AppFrameworkInfo.plist @@ -25,6 +25,6 @@ arm64 MinimumOSVersion - 9.0 + 11.0 diff --git a/packages/url_launcher/url_launcher/example/ios/Podfile b/packages/url_launcher/url_launcher/example/ios/Podfile index f7d6a5e68c3a..d207307f86d7 100644 --- a/packages/url_launcher/url_launcher/example/ios/Podfile +++ b/packages/url_launcher/url_launcher/example/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -# platform :ios, '9.0' +# platform :ios, '11.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/packages/url_launcher/url_launcher/example/ios/Runner.xcodeproj/project.pbxproj b/packages/url_launcher/url_launcher/example/ios/Runner.xcodeproj/project.pbxproj index 7855640c017e..0b8010748e09 100644 --- a/packages/url_launcher/url_launcher/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/url_launcher/url_launcher/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -169,7 +169,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1100; + LastUpgradeCheck = 1300; ORGANIZATIONNAME = "The Flutter Authors"; TargetAttributes = { 97C146ED1CF9000F007C117D = { @@ -213,10 +213,12 @@ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( @@ -227,6 +229,7 @@ }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -340,7 +343,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -390,7 +393,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/packages/url_launcher/url_launcher/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/url_launcher/url_launcher/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index c5f1a9de4a30..ad0ebfab1b88 100644 --- a/packages/url_launcher/url_launcher/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/url_launcher/url_launcher/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ UIViewControllerBasedStatusBarAppearance + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + diff --git a/packages/url_launcher/url_launcher/pubspec.yaml b/packages/url_launcher/url_launcher/pubspec.yaml index e2b3ed534dbf..e4f6d4c7c5c4 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. Supports web, phone, SMS, and email schemes. repository: https://github.com/flutter/plugins/tree/main/packages/url_launcher/url_launcher issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+url_launcher%22 -version: 6.1.8 +version: 6.1.9 environment: sdk: ">=2.14.0 <3.0.0" diff --git a/packages/url_launcher/url_launcher_ios/CHANGELOG.md b/packages/url_launcher/url_launcher_ios/CHANGELOG.md index 058520764c88..86546d45566d 100644 --- a/packages/url_launcher/url_launcher_ios/CHANGELOG.md +++ b/packages/url_launcher/url_launcher_ios/CHANGELOG.md @@ -1,6 +1,6 @@ -## NEXT +## 6.1.0 -* Updates minimum Flutter version to 3.0. +* Updates minimum Flutter version to 3.3 and iOS 11. ## 6.0.18 diff --git a/packages/url_launcher/url_launcher_ios/example/ios/Flutter/AppFrameworkInfo.plist b/packages/url_launcher/url_launcher_ios/example/ios/Flutter/AppFrameworkInfo.plist index 3a9c234f96d4..9b41e7d87980 100644 --- a/packages/url_launcher/url_launcher_ios/example/ios/Flutter/AppFrameworkInfo.plist +++ b/packages/url_launcher/url_launcher_ios/example/ios/Flutter/AppFrameworkInfo.plist @@ -25,6 +25,6 @@ arm64 MinimumOSVersion - 9.0 + 11.0 diff --git a/packages/url_launcher/url_launcher_ios/example/ios/Podfile b/packages/url_launcher/url_launcher_ios/example/ios/Podfile index 3924e59aa0f9..ec43b513b0d1 100644 --- a/packages/url_launcher/url_launcher_ios/example/ios/Podfile +++ b/packages/url_launcher/url_launcher_ios/example/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -# platform :ios, '9.0' +# platform :ios, '11.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/packages/url_launcher/url_launcher_ios/example/ios/Runner.xcodeproj/project.pbxproj b/packages/url_launcher/url_launcher_ios/example/ios/Runner.xcodeproj/project.pbxproj index 595f85d9a75b..d61abc724469 100644 --- a/packages/url_launcher/url_launcher_ios/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/url_launcher/url_launcher_ios/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -269,7 +269,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1100; + LastUpgradeCheck = 1300; ORGANIZATIONNAME = "The Flutter Authors"; TargetAttributes = { 97C146ED1CF9000F007C117D = { @@ -339,10 +339,12 @@ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( @@ -353,6 +355,7 @@ }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -517,7 +520,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -567,7 +570,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/packages/url_launcher/url_launcher_ios/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/url_launcher/url_launcher_ios/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index c5f1a9de4a30..ad0ebfab1b88 100644 --- a/packages/url_launcher/url_launcher_ios/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/url_launcher/url_launcher_ios/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ UIViewControllerBasedStatusBarAppearance + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + diff --git a/packages/url_launcher/url_launcher_ios/example/lib/main.dart b/packages/url_launcher/url_launcher_ios/example/lib/main.dart index 08a03063a880..f01624ff87c6 100644 --- a/packages/url_launcher/url_launcher_ios/example/lib/main.dart +++ b/packages/url_launcher/url_launcher_ios/example/lib/main.dart @@ -14,7 +14,7 @@ void main() { } class MyApp extends StatelessWidget { - const MyApp({Key? key}) : super(key: key); + const MyApp({super.key}); @override Widget build(BuildContext context) { @@ -29,7 +29,7 @@ class MyApp extends StatelessWidget { } class MyHomePage extends StatefulWidget { - const MyHomePage({Key? key, required this.title}) : super(key: key); + const MyHomePage({super.key, required this.title}); final String title; @override diff --git a/packages/url_launcher/url_launcher_ios/example/pubspec.yaml b/packages/url_launcher/url_launcher_ios/example/pubspec.yaml index d170d3617442..21b191ad0cce 100644 --- a/packages/url_launcher/url_launcher_ios/example/pubspec.yaml +++ b/packages/url_launcher/url_launcher_ios/example/pubspec.yaml @@ -3,8 +3,8 @@ description: Demonstrates how to use the url_launcher plugin. publish_to: none environment: - sdk: ">=2.14.0 <3.0.0" - flutter: ">=3.0.0" + sdk: '>=2.18.0 <3.0.0' + flutter: ">=3.3.0" dependencies: flutter: diff --git a/packages/url_launcher/url_launcher_ios/ios/Classes/FLTURLLauncherPlugin.m b/packages/url_launcher/url_launcher_ios/ios/Classes/FLTURLLauncherPlugin.m index af720c87b8b2..375d5e2a2354 100644 --- a/packages/url_launcher/url_launcher_ios/ios/Classes/FLTURLLauncherPlugin.m +++ b/packages/url_launcher/url_launcher_ios/ios/Classes/FLTURLLauncherPlugin.m @@ -6,7 +6,6 @@ #import "FLTURLLauncherPlugin.h" -API_AVAILABLE(ios(9.0)) @interface FLTURLLaunchSession : NSObject @property(copy, nonatomic) FlutterResult flutterResult; @@ -30,7 +29,7 @@ - (instancetype)initWithUrl:url withFlutterResult:result { } - (void)safariViewController:(SFSafariViewController *)controller - didCompleteInitialLoad:(BOOL)didLoadSuccessfully API_AVAILABLE(ios(9.0)) { + didCompleteInitialLoad:(BOOL)didLoadSuccessfully { if (didLoadSuccessfully) { self.flutterResult(@YES); } else { @@ -41,7 +40,7 @@ - (void)safariViewController:(SFSafariViewController *)controller } } -- (void)safariViewControllerDidFinish:(SFSafariViewController *)controller API_AVAILABLE(ios(9.0)) { +- (void)safariViewControllerDidFinish:(SFSafariViewController *)controller { [controller dismissViewControllerAnimated:YES completion:nil]; self.didFinish(); } @@ -52,7 +51,6 @@ - (void)close { @end -API_AVAILABLE(ios(9.0)) @interface FLTURLLauncherPlugin () @property(strong, nonatomic) FLTURLLaunchSession *currentSession; @@ -99,24 +97,16 @@ - (void)launchURL:(NSString *)urlString NSURL *url = [NSURL URLWithString:urlString]; UIApplication *application = [UIApplication sharedApplication]; - if (@available(iOS 10.0, *)) { - NSNumber *universalLinksOnly = call.arguments[@"universalLinksOnly"] ?: @0; - NSDictionary *options = @{UIApplicationOpenURLOptionUniversalLinksOnly : universalLinksOnly}; - [application openURL:url - options:options - completionHandler:^(BOOL success) { - result(@(success)); - }]; - } else { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - BOOL success = [application openURL:url]; -#pragma clang diagnostic pop - result(@(success)); - } + NSNumber *universalLinksOnly = call.arguments[@"universalLinksOnly"] ?: @0; + NSDictionary *options = @{UIApplicationOpenURLOptionUniversalLinksOnly : universalLinksOnly}; + [application openURL:url + options:options + completionHandler:^(BOOL success) { + result(@(success)); + }]; } -- (void)launchURLInVC:(NSString *)urlString result:(FlutterResult)result API_AVAILABLE(ios(9.0)) { +- (void)launchURLInVC:(NSString *)urlString result:(FlutterResult)result { NSURL *url = [NSURL URLWithString:urlString]; self.currentSession = [[FLTURLLaunchSession alloc] initWithUrl:url withFlutterResult:result]; __weak typeof(self) weakSelf = self; @@ -128,7 +118,7 @@ - (void)launchURLInVC:(NSString *)urlString result:(FlutterResult)result API_AVA completion:nil]; } -- (void)closeWebViewWithResult:(FlutterResult)result API_AVAILABLE(ios(9.0)) { +- (void)closeWebViewWithResult:(FlutterResult)result { if (self.currentSession != nil) { [self.currentSession close]; } diff --git a/packages/url_launcher/url_launcher_ios/ios/url_launcher_ios.podspec b/packages/url_launcher/url_launcher_ios/ios/url_launcher_ios.podspec index 1c0e81964252..9c265694018e 100644 --- a/packages/url_launcher/url_launcher_ios/ios/url_launcher_ios.podspec +++ b/packages/url_launcher/url_launcher_ios/ios/url_launcher_ios.podspec @@ -16,7 +16,6 @@ A Flutter plugin for making the underlying platform (Android or iOS) launch a UR s.source_files = 'Classes/**/*' s.public_header_files = 'Classes/**/*.h' s.dependency 'Flutter' - - s.platform = :ios, '9.0' + s.platform = :ios, '11.0' s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } end diff --git a/packages/url_launcher/url_launcher_ios/pubspec.yaml b/packages/url_launcher/url_launcher_ios/pubspec.yaml index 5817a9a44051..5a5c4bdc0514 100644 --- a/packages/url_launcher/url_launcher_ios/pubspec.yaml +++ b/packages/url_launcher/url_launcher_ios/pubspec.yaml @@ -2,11 +2,11 @@ name: url_launcher_ios description: iOS implementation of the url_launcher plugin. repository: https://github.com/flutter/plugins/tree/main/packages/url_launcher/url_launcher_ios issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+url_launcher%22 -version: 6.0.18 +version: 6.1.0 environment: - sdk: ">=2.14.0 <3.0.0" - flutter: ">=3.0.0" + sdk: '>=2.18.0 <3.0.0' + flutter: ">=3.3.0" flutter: plugin: From 6f985d57b04b268d7e99e579e486042991bf2a8c Mon Sep 17 00:00:00 2001 From: stuartmorgan Date: Mon, 6 Feb 2023 17:49:49 -0800 Subject: [PATCH 083/130] Redistribute ownership of cross-platform plugin components (#7093) --- CODEOWNERS | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index f4d6ede3fc43..86c13b4502ff 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -9,15 +9,15 @@ packages/camera/** @bparrishMines packages/file_selector/** @stuartmorgan packages/google_maps_flutter/** @stuartmorgan packages/google_sign_in/** @stuartmorgan -packages/image_picker/** @stuartmorgan +packages/image_picker/** @tarrinneal packages/in_app_purchase/** @bparrishMines packages/local_auth/** @stuartmorgan -packages/path_provider/** @gaaclarke +packages/path_provider/** @stuartmorgan packages/plugin_platform_interface/** @stuartmorgan -packages/quick_actions/** @stuartmorgan +packages/quick_actions/** @bparrishMines packages/shared_preferences/** @tarrinneal packages/url_launcher/** @stuartmorgan -packages/video_player/** @gaaclarke +packages/video_player/** @tarrinneal packages/webview_flutter/** @bparrishMines # Sub-package-level rules. These should stay last, since the last matching From 40c2e116994f92005814d4fd1db976294cc53eda Mon Sep 17 00:00:00 2001 From: Tarrin Neal Date: Tue, 7 Feb 2023 10:01:31 -0800 Subject: [PATCH 084/130] [local_auth_android] update java complie sdk version to green tree (#7121) * update java complie sdk version * again --- packages/local_auth/local_auth/example/android/app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/local_auth/local_auth/example/android/app/build.gradle b/packages/local_auth/local_auth/example/android/app/build.gradle index 3c6eca7ce8a7..0146852feb44 100644 --- a/packages/local_auth/local_auth/example/android/app/build.gradle +++ b/packages/local_auth/local_auth/example/android/app/build.gradle @@ -25,7 +25,7 @@ apply plugin: 'com.android.application' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { - compileSdkVersion 31 + compileSdkVersion 33 lintOptions { disable 'InvalidPackage' From 81c6fd07a8e704f97f3d1c94ff3f6a1240e33e97 Mon Sep 17 00:00:00 2001 From: Reid Baker Date: Tue, 7 Feb 2023 13:41:14 -0500 Subject: [PATCH 085/130] Update release tooling to give a workaround for predictable failing case https://github.com/flutter/flutter/issues/120116 (#7111) --- script/tool/lib/src/update_release_info_command.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/script/tool/lib/src/update_release_info_command.dart b/script/tool/lib/src/update_release_info_command.dart index 8d7ceb84d31d..240ae72eed71 100644 --- a/script/tool/lib/src/update_release_info_command.dart +++ b/script/tool/lib/src/update_release_info_command.dart @@ -107,6 +107,10 @@ class UpdateReleaseInfoCommand extends PackageLoopingCommand { break; case _versionMinimal: final GitVersionFinder gitVersionFinder = await retrieveVersionFinder(); + // If the line below fails with "Not a valid object name FETCH_HEAD" + // run "git fetch", FETCH_HEAD is a temporary reference that only exists + // after a fetch. This can happen when a branch is made locally and + // pushed but never fetched. _changedFiles = await gitVersionFinder.getChangedFiles(); // Anothing other than a fixed change is null. _versionChange = null; From f59c08db3f2735f560fb96ac657f8280bd61e443 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Tue, 7 Feb 2023 15:42:15 -0500 Subject: [PATCH 086/130] Roll Flutter from 3c3c9a1bd98f to e8eac0d047cd (21 revisions) (#7122) * 1bec87b4a Update Android TESTOWNERS (flutter/flutter#119960) * 7bf1e99ea Roll Plugins from 9302d87ee545 to d065e4e0a82a (6 revisions) (flutter/flutter#120084) * 40b5e4cb5 Added "insertAll" and "removeAll" methods to AnimatedList (flutter/flutter#115545) * ec524ed06 Fix flutter_tools stuck when using custom LLDB prompt (flutter/flutter#119443) * 575ced6c5 Fix context menu web examples (flutter/flutter#120104) * e627e8d84 Force web_tool_tests to run on x64 builders (flutter/flutter#120128) * e62abfae6 Remove unreachable_from_main linter rule (flutter/flutter#120110) * 71971f223 Run `verify_binaries_codesigned` task on release branches (flutter/flutter#120141) * 845f7bb42 Roll Flutter Engine from 2a104cdfcdf8 to 165126e7034c (13 revisions) (flutter/flutter#120150) * cf3fc0177 remove deprecated accentTextTheme and accentIconTheme from ThemeData (flutter/flutter#119360) * 1d0cbbb24 fix a [SelectableRegion] crash bug (flutter/flutter#120076) * a808ba054 39f5e4cba Roll Skia from 7e2c9f54c0fd to 419bb63e733d (1 revision) (flutter/engine#39447) (flutter/flutter#120159) * 7a6f1d81d M3 segmented buttons token fixes (flutter/flutter#120095) * 16441f4bf 5aadda2f4 Roll Skia from 419bb63e733d to 83da27e4cd3a (1 revision) (flutter/engine#39448) (flutter/flutter#120172) * a6ea64457 Fix cut button creation in 'buttonItemsForToolbarOptions' (flutter/flutter#119822) * d7f742e90 Roll Flutter Engine from 5aadda2f40b1 to e432b82f49f3 (3 revisions) (flutter/flutter#120191) * 3f5b105fc Roll Plugins from d065e4e0a82a to 6f985d57b04b (10 revisions) (flutter/flutter#120193) * e03029ef6 [web] Move JS content to its own `.js` files (flutter/flutter#117691) * f2e89755e b67690f69 Roll Skia from 6babb6a1afe6 to 3b1401c4870d (1 revision) (flutter/engine#39455) (flutter/flutter#120198) * da36bd6fc Stop recursively including assets from asset folders (flutter/flutter#120167) * e8eac0d04 Update `ExpansionTile` to support Material 3 & add an example (flutter/flutter#119712) --- .ci/flutter_master.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/flutter_master.version b/.ci/flutter_master.version index 3189386f5fbf..a8a59c53e9d5 100644 --- a/.ci/flutter_master.version +++ b/.ci/flutter_master.version @@ -1 +1 @@ -3c3c9a1bd98f464fc2c25751dac345dd61545ebd +e8eac0d047cd44feabf0a62ab06bcecbbcf24b1b From 84ad624e32417e9d34239e0df8408a42bd163757 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Wed, 8 Feb 2023 15:36:06 -0500 Subject: [PATCH 087/130] Manual roll Flutter from e8eac0d047cd to 2303f42250b1 (23 revisions) (#7132) * 1c225675c Update to v0.158 of the token database. (flutter/flutter#120149) * 0b0450fbf Web tab selection (flutter/flutter#119583) * 108958886 un-pin package:intl (flutter/flutter#119900) * 5be7f6639 f310ffd14 Roll Skia from 3b1401c4870d to 87dbc81b421f (4 revisions) (flutter/engine#39457) (flutter/flutter#120214) * aed9b4adc Revert "Revert "Fix unable to find bundled Java version (#119244)" (#119981)" (flutter/flutter#120107) * 98b3e48ed Fix hang on successful dev/bots/analyze.dart (flutter/flutter#117660) * cd125e1f7 Add test for RenderProxyBoxMixin; clarify doc, resolve TODO (flutter/flutter#117664) * f94fa7ea2 Roll Flutter Engine from f310ffd1461a to 7098858dc0a5 (9 revisions) (flutter/flutter#120251) * 99b6bd8c0 Add support for extending selection to paragraph on ctrl + shift + arrow up/down on Non-Apple platforms (flutter/flutter#120151) * 1e6e6d41e Revert "Roll Flutter Engine from f310ffd1461a to 7098858dc0a5 (9 revisions) (#120251)" (flutter/flutter#120257) * 6e7f58037 fix a TextFormField bug (flutter/flutter#120182) * 3f98c0f8f Add trackOutlineColor for Switch and SwitchListTile (flutter/flutter#120140) * 7f578fb01 Revert "Stop recursively including assets from asset folders (#120167)" (flutter/flutter#120283) * d8154fde7 Manual roll Flutter Engine from f310ffd1461a to bdc5b6b768f6 (12 revisions) (flutter/flutter#120261) * 75ca31b0e Correct Badge interpretation of its alignment parameter (flutter/flutter#119853) * 0588b925a Removed "if" on resolving text color at "SnackBarAction" (flutter/flutter#120050) * 0a97ef85c Fix BottomAppBar & BottomSheet M3 shadow (flutter/flutter#119819) * bfea22db5 Roll Plugins from 6f985d57b04b to f59c08db3f27 (3 revisions) (flutter/flutter#120303) * ec289f1eb Don't call `PlatformViewCreatedCallback`s after `AndroidViewController` is disposed (flutter/flutter#116854) * 3a514175d Remove Android spell check integration test (flutter/flutter#120144) * 51227a9a5 Add missing parameters to `RadioListTile` (flutter/flutter#120117) * 212bac80d Revert "Update `ExpansionTile` to support Material 3 & add an example (#119712)" (flutter/flutter#120300) * 2303f4225 Manual roll Flutter Engine from bdc5b6b768f6 to cc4ca6a06ab3 (8 revisions) (flutter/flutter#120309) --- .ci/flutter_master.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/flutter_master.version b/.ci/flutter_master.version index a8a59c53e9d5..ee2466f94359 100644 --- a/.ci/flutter_master.version +++ b/.ci/flutter_master.version @@ -1 +1 @@ -e8eac0d047cd44feabf0a62ab06bcecbbcf24b1b +2303f42250b11cd4e553660d89483ead57bc14d1 From d8812bc75f7a8484a171025caf7e427cb8b608db Mon Sep 17 00:00:00 2001 From: stuartmorgan Date: Wed, 8 Feb 2023 18:01:07 -0800 Subject: [PATCH 088/130] [ci] Complete architecture switch for iOS (#7066) Enables the new architecture tests for iOS and turns down the old ones. Platform tests are now run on ARM, and the build-all test is run on Intel for coverage of building on both architectures. --- .ci.yaml | 143 +++++++------------------------------------------------ 1 file changed, 16 insertions(+), 127 deletions(-) diff --git a/.ci.yaml b/.ci.yaml index c5b6adff9108..14d1e16cd7de 100644 --- a/.ci.yaml +++ b/.ci.yaml @@ -64,7 +64,7 @@ targets: target_file: macos_lint_podspecs.yaml ### macOS desktop tasks ### - # macos-platform_tests builds all the plugins on ARM, so this build is run + # macos_platform_tests builds all the plugins on ARM, so this build is run # on Intel to give us build coverage of both host types. - name: Mac_x64 build_all_plugins master recipe: plugins/plugins @@ -104,29 +104,9 @@ targets: target_file: macos_platform_tests.yaml ### iOS tasks ### - # TODO(stuartmorgan): Swap the architecture of this and ios_platform_tests_* - # once simulator tests are reliable on the ARM infrastructure. See discussion - # at https://github.com/flutter/plugins/pull/5693#issuecomment-1126011089 - - name: Mac_arm64 ios_build_all_plugins master - recipe: plugins/plugins - timeout: 30 - properties: - channel: master - add_recipes_cq: "true" - version_file: flutter_master.version - target_file: ios_build_all_plugins.yaml - - - name: Mac_arm64 ios_build_all_plugins stable - recipe: plugins/plugins - timeout: 30 - properties: - channel: stable - add_recipes_cq: "true" - version_file: flutter_stable.version - target_file: ios_build_all_plugins.yaml - + # ios_platform_tests builds all the plugins on ARM, so this build is run + # on Intel to give us build coverage of both host types. - name: Mac_x64 ios_build_all_plugins master - bringup: true # New task, replaces ARM version recipe: plugins/plugins timeout: 30 properties: @@ -136,7 +116,6 @@ targets: target_file: ios_build_all_plugins.yaml - name: Mac_x64 ios_build_all_plugins stable - bringup: true # New task, replaces ARM version recipe: plugins/plugins timeout: 30 properties: @@ -145,49 +124,12 @@ targets: version_file: flutter_stable.version target_file: ios_build_all_plugins.yaml - # TODO(stuartmorgan): Swap the architecture of this and ios_build_all_plugins - # once simulator tests are reliable on the ARM infrastructure. See discussion - # at https://github.com/flutter/plugins/pull/5693#issuecomment-1126011089 - - name: Mac_x64 ios_platform_tests_1_of_4 master - recipe: plugins/plugins - timeout: 60 - properties: - add_recipes_cq: "true" - version_file: flutter_master.version - target_file: ios_platform_tests.yaml - package_sharding: "--shardIndex 0 --shardCount 4" - - - name: Mac_x64 ios_platform_tests_2_of_4 master - recipe: plugins/plugins - timeout: 60 - properties: - add_recipes_cq: "true" - version_file: flutter_master.version - target_file: ios_platform_tests.yaml - package_sharding: "--shardIndex 1 --shardCount 4" - - - name: Mac_x64 ios_platform_tests_3_of_4 master - recipe: plugins/plugins - timeout: 60 - properties: - add_recipes_cq: "true" - version_file: flutter_master.version - target_file: ios_platform_tests.yaml - package_sharding: "--shardIndex 2 --shardCount 4" - - - name: Mac_x64 ios_platform_tests_4_of_4 master - recipe: plugins/plugins - timeout: 60 - properties: - add_recipes_cq: "true" - version_file: flutter_master.version - target_file: ios_platform_tests.yaml - package_sharding: "--shardIndex 3 --shardCount 4" - + # TODO(stuartmorgan): Change all of the ios_platform_tests_* task timeouts + # to 60 minutes once https://github.com/flutter/flutter/issues/119750 is + # fixed. - name: Mac_arm64 ios_platform_tests_shard_1 master - plugins - bringup: true # New task; will replace Intel version recipe: plugins/plugins - timeout: 60 + timeout: 120 properties: add_recipes_cq: "true" version_file: flutter_master.version @@ -195,9 +137,8 @@ targets: package_sharding: "--shardIndex 0 --shardCount 5" - name: Mac_arm64 ios_platform_tests_shard_2 master - plugins - bringup: true # New task; will replace Intel version recipe: plugins/plugins - timeout: 60 + timeout: 120 properties: add_recipes_cq: "true" version_file: flutter_master.version @@ -205,9 +146,8 @@ targets: package_sharding: "--shardIndex 1 --shardCount 5" - name: Mac_arm64 ios_platform_tests_shard_3 master - plugins - bringup: true # New task; will replace Intel version recipe: plugins/plugins - timeout: 60 + timeout: 120 properties: add_recipes_cq: "true" version_file: flutter_master.version @@ -215,9 +155,8 @@ targets: package_sharding: "--shardIndex 2 --shardCount 5" - name: Mac_arm64 ios_platform_tests_shard_4 master - plugins - bringup: true # New task; will replace Intel version recipe: plugins/plugins - timeout: 60 + timeout: 120 properties: add_recipes_cq: "true" version_file: flutter_master.version @@ -225,9 +164,8 @@ targets: package_sharding: "--shardIndex 3 --shardCount 5" - name: Mac_arm64 ios_platform_tests_shard_5 master - plugins - bringup: true # New task; will replace Intel version recipe: plugins/plugins - timeout: 60 + timeout: 120 properties: add_recipes_cq: "true" version_file: flutter_master.version @@ -235,55 +173,10 @@ targets: package_sharding: "--shardIndex 4 --shardCount 5" # Don't run full platform tests on both channels in pre-submit. - - name: Mac_x64 ios_platform_tests_1_of_4 stable - recipe: plugins/plugins - presubmit: false - timeout: 60 - properties: - channel: stable - add_recipes_cq: "true" - version_file: flutter_stable.version - target_file: ios_platform_tests.yaml - package_sharding: "--shardIndex 0 --shardCount 4" - - - name: Mac_x64 ios_platform_tests_2_of_4 stable - recipe: plugins/plugins - presubmit: false - timeout: 60 - properties: - channel: stable - add_recipes_cq: "true" - version_file: flutter_stable.version - target_file: ios_platform_tests.yaml - package_sharding: "--shardIndex 1 --shardCount 4" - - - name: Mac_x64 ios_platform_tests_3_of_4 stable - recipe: plugins/plugins - presubmit: false - timeout: 60 - properties: - channel: stable - add_recipes_cq: "true" - version_file: flutter_stable.version - target_file: ios_platform_tests.yaml - package_sharding: "--shardIndex 2 --shardCount 4" - - - name: Mac_x64 ios_platform_tests_4_of_4 stable - recipe: plugins/plugins - presubmit: false - timeout: 60 - properties: - channel: stable - add_recipes_cq: "true" - version_file: flutter_stable.version - target_file: ios_platform_tests.yaml - package_sharding: "--shardIndex 3 --shardCount 4" - - name: Mac_arm64 ios_platform_tests_shard_1 stable - plugins - bringup: true # New task; will replace Intel version recipe: plugins/plugins presubmit: false - timeout: 60 + timeout: 120 properties: channel: stable add_recipes_cq: "true" @@ -292,10 +185,9 @@ targets: package_sharding: "--shardIndex 0 --shardCount 5" - name: Mac_arm64 ios_platform_tests_shard_2 stable - plugins - bringup: true # New task; will replace Intel version recipe: plugins/plugins presubmit: false - timeout: 60 + timeout: 120 properties: channel: stable add_recipes_cq: "true" @@ -304,10 +196,9 @@ targets: package_sharding: "--shardIndex 1 --shardCount 5" - name: Mac_arm64 ios_platform_tests_shard_3 stable - plugins - bringup: true # New task; will replace Intel version recipe: plugins/plugins presubmit: false - timeout: 60 + timeout: 120 properties: channel: stable add_recipes_cq: "true" @@ -316,10 +207,9 @@ targets: package_sharding: "--shardIndex 2 --shardCount 5" - name: Mac_arm64 ios_platform_tests_shard_4 stable - plugins - bringup: true # New task; will replace Intel version recipe: plugins/plugins presubmit: false - timeout: 60 + timeout: 120 properties: channel: stable add_recipes_cq: "true" @@ -328,10 +218,9 @@ targets: package_sharding: "--shardIndex 3 --shardCount 5" - name: Mac_arm64 ios_platform_tests_shard_5 stable - plugins - bringup: true # New task; will replace Intel version recipe: plugins/plugins presubmit: false - timeout: 60 + timeout: 120 properties: channel: stable add_recipes_cq: "true" From 703111040f01f8264fda8a5dbe2b14766d0415f3 Mon Sep 17 00:00:00 2001 From: Balvinder Singh Gambhir Date: Thu, 9 Feb 2023 07:31:51 +0530 Subject: [PATCH 089/130] [google_maps_flutter_android] Fixes points losing precision when converting to LatLng (#7101) * fix points losing precision when converted to lat long * add changelog * fix format * fix format * fix name * fix format * add license --- .../google_maps_flutter_android/CHANGELOG.md | 3 +- .../flutter/plugins/googlemaps/Convert.java | 6 ++-- .../plugins/googlemaps/ConvertTest.java | 29 +++++++++++++++++++ .../google_maps_flutter_android/pubspec.yaml | 2 +- 4 files changed, 36 insertions(+), 4 deletions(-) create mode 100644 packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/ConvertTest.java diff --git a/packages/google_maps_flutter/google_maps_flutter_android/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter_android/CHANGELOG.md index 6e596c135f38..35dd55033b78 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/CHANGELOG.md +++ b/packages/google_maps_flutter/google_maps_flutter_android/CHANGELOG.md @@ -1,5 +1,6 @@ -## NEXT +## 2.4.4 +* Fixes Points losing precision when converting to LatLng * Updates minimum Flutter version to 3.0. ## 2.4.3 diff --git a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java index 72c6959fe55e..22c8f4d24be6 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java +++ b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java @@ -7,6 +7,7 @@ import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Point; +import androidx.annotation.VisibleForTesting; import com.google.android.gms.maps.CameraUpdate; import com.google.android.gms.maps.CameraUpdateFactory; import com.google.android.gms.maps.model.BitmapDescriptor; @@ -592,13 +593,14 @@ static String interpretCircleOptions(Object o, CircleOptionsSink sink) { } } - private static List toPoints(Object o) { + @VisibleForTesting + static List toPoints(Object o) { final List data = toList(o); final List points = new ArrayList<>(data.size()); for (Object rawPoint : data) { final List point = toList(rawPoint); - points.add(new LatLng(toFloat(point.get(0)), toFloat(point.get(1)))); + points.add(new LatLng(toDouble(point.get(0)), toDouble(point.get(1)))); } return points; } diff --git a/packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/ConvertTest.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/ConvertTest.java new file mode 100644 index 000000000000..0d635170c1f3 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/ConvertTest.java @@ -0,0 +1,29 @@ +// Copyright 2013 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.googlemaps; + +import com.google.android.gms.maps.model.LatLng; +import java.util.ArrayList; +import java.util.List; +import org.junit.Assert; +import org.junit.Test; + +public class ConvertTest { + + @Test + public void ConvertToPointsConvertsThePointsWithFullPrecision() { + double latitude = 43.03725568057; + double longitude = -87.90466904649; + ArrayList point = new ArrayList(); + point.add(latitude); + point.add(longitude); + ArrayList> pointsList = new ArrayList<>(); + pointsList.add(point); + List latLngs = Convert.toPoints(pointsList); + LatLng latLng = latLngs.get(0); + Assert.assertEquals(latitude, latLng.latitude, 1e-15); + Assert.assertEquals(longitude, latLng.longitude, 1e-15); + } +} diff --git a/packages/google_maps_flutter/google_maps_flutter_android/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_android/pubspec.yaml index d67e85f15e9a..0e2842bf24f2 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter_android/pubspec.yaml @@ -2,7 +2,7 @@ name: google_maps_flutter_android description: Android implementation of the google_maps_flutter plugin. repository: https://github.com/flutter/plugins/tree/main/packages/google_maps_flutter/google_maps_flutter_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+maps%22 -version: 2.4.3 +version: 2.4.4 environment: sdk: ">=2.14.0 <3.0.0" From 435c46f2111b899a8435569e20c820c5b900c0bb Mon Sep 17 00:00:00 2001 From: gmackall <34871572+gmackall@users.noreply.github.com> Date: Wed, 8 Feb 2023 18:01:54 -0800 Subject: [PATCH 090/130] [camera] availableCameras() implementation for CameraX re-write (#6945) * Adding availableCameras to camerax camera implementation * Marking loop variables as nullable in availableCameras * adding testing for android_camerax availableCameras() * marking variable final * removing commented import * cleaning up tests for availableCamera camerax re-write * chaning import * respondingt to review comments * fixing tests * fixing presubmit issues * adding changelog message * formatting changes for presubmit tests * another formatting change for presubmit checks --------- Co-authored-by: Gray Mackall --- .../camera_android_camerax/CHANGELOG.md | 1 + .../camerax/CameraSelectorHostApiImpl.java | 5 -- .../lib/src/android_camera_camerax.dart | 54 +++++++++++- .../test/android_camera_camerax_test.dart | 88 +++++++++++++++++++ .../android_camera_camerax_test.mocks.dart | 79 +++++++++++++++++ 5 files changed, 221 insertions(+), 6 deletions(-) create mode 100644 packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart create mode 100644 packages/camera/camera_android_camerax/test/android_camera_camerax_test.mocks.dart diff --git a/packages/camera/camera_android_camerax/CHANGELOG.md b/packages/camera/camera_android_camerax/CHANGELOG.md index 080240a64f42..50fdf586c5e7 100644 --- a/packages/camera/camera_android_camerax/CHANGELOG.md +++ b/packages/camera/camera_android_camerax/CHANGELOG.md @@ -9,3 +9,4 @@ * Bump CameraX version to 1.3.0-alpha03 and Kotlin version to 1.8.0. * Changes instance manager to allow the separate creation of identical objects. * Adds Preview and Surface classes, along with other methods needed to implement camera preview. +* Adds implementation of availableCameras() diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraSelectorHostApiImpl.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraSelectorHostApiImpl.java index 0528d584d26e..603f7cf78def 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraSelectorHostApiImpl.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraSelectorHostApiImpl.java @@ -55,14 +55,9 @@ public List filter(@NonNull Long identifier, @NonNull List cameraInf } List filteredCameraInfos = cameraSelector.filter(cameraInfosForFilter); - final CameraInfoFlutterApiImpl cameraInfoFlutterApiImpl = - new CameraInfoFlutterApiImpl(binaryMessenger, instanceManager); List filteredCameraInfosIds = new ArrayList(); for (CameraInfo cameraInfo : filteredCameraInfos) { - if (!instanceManager.containsInstance(cameraInfo)) { - cameraInfoFlutterApiImpl.create(cameraInfo, result -> {}); - } Long filteredCameraInfoId = instanceManager.getIdentifierForStrongReference(cameraInfo); filteredCameraInfosIds.add(filteredCameraInfoId); } diff --git a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart index f03273861793..300d6717fb46 100644 --- a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart +++ b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart @@ -5,6 +5,11 @@ import 'dart:async'; import 'package:camera_platform_interface/camera_platform_interface.dart'; +import 'package:flutter/foundation.dart'; + +import 'camera_info.dart'; +import 'camera_selector.dart'; +import 'process_camera_provider.dart'; /// The Android implementation of [CameraPlatform] that uses the CameraX library. class AndroidCameraCameraX extends CameraPlatform { @@ -13,9 +18,56 @@ class AndroidCameraCameraX extends CameraPlatform { CameraPlatform.instance = AndroidCameraCameraX(); } + /// ProcessCameraProvider used to get list of cameras. Visible only for testing. + @visibleForTesting + ProcessCameraProvider? processCameraProvider; + + /// Camera selector used to determine which CameraInfos are back cameras. + @visibleForTesting + CameraSelector? backCameraSelector = CameraSelector.getDefaultBackCamera(); + + /// Camera selector used to determine which CameraInfos are back cameras. + @visibleForTesting + CameraSelector? frontCameraSelector = CameraSelector.getDefaultFrontCamera(); + /// Returns list of all available cameras and their descriptions. @override Future> availableCameras() async { - throw UnimplementedError('availableCameras() is not implemented.'); + final List cameraDescriptions = []; + + processCameraProvider ??= await ProcessCameraProvider.getInstance(); + final List cameraInfos = + await processCameraProvider!.getAvailableCameraInfos(); + + CameraLensDirection? cameraLensDirection; + int cameraCount = 0; + int? cameraSensorOrientation; + String? cameraName; + + for (final CameraInfo cameraInfo in cameraInfos) { + // Determine the lens direction by filtering the CameraInfo + // TODO(gmackall): replace this with call to CameraInfo.getLensFacing when changes containing that method are available + if ((await backCameraSelector!.filter([cameraInfo])) + .isNotEmpty) { + cameraLensDirection = CameraLensDirection.back; + } else if ((await frontCameraSelector!.filter([cameraInfo])) + .isNotEmpty) { + cameraLensDirection = CameraLensDirection.front; + } else { + //Skip this CameraInfo as its lens direction is unknown + continue; + } + + cameraSensorOrientation = await cameraInfo.getSensorRotationDegrees(); + cameraName = 'Camera $cameraCount'; + cameraCount++; + + cameraDescriptions.add(CameraDescription( + name: cameraName, + lensDirection: cameraLensDirection, + sensorOrientation: cameraSensorOrientation)); + } + + return cameraDescriptions; } } diff --git a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart new file mode 100644 index 000000000000..8b16c64c6ede --- /dev/null +++ b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart @@ -0,0 +1,88 @@ +// Copyright 2013 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. + +import 'package:camera_android_camerax/camera_android_camerax.dart'; +import 'package:camera_android_camerax/src/camera_info.dart'; +import 'package:camera_android_camerax/src/camera_selector.dart'; +import 'package:camera_android_camerax/src/process_camera_provider.dart'; +import 'package:camera_platform_interface/camera_platform_interface.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; + +import 'android_camera_camerax_test.mocks.dart'; + +@GenerateMocks([ + ProcessCameraProvider, + CameraSelector, + CameraInfo, +]) +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + test('Should fetch CameraDescription instances for available cameras', + () async { + // Arrange + final List returnData = [ + { + 'name': 'Camera 0', + 'lensFacing': 'back', + 'sensorOrientation': 0 + }, + { + 'name': 'Camera 1', + 'lensFacing': 'front', + 'sensorOrientation': 90 + } + ]; + + //Create mocks to use + final MockProcessCameraProvider mockProcessCameraProvider = + MockProcessCameraProvider(); + final MockCameraSelector mockBackCameraSelector = MockCameraSelector(); + final MockCameraSelector mockFrontCameraSelector = MockCameraSelector(); + final MockCameraInfo mockFrontCameraInfo = MockCameraInfo(); + final MockCameraInfo mockBackCameraInfo = MockCameraInfo(); + AndroidCameraCameraX.registerWith(); + + //Set class level ProcessCameraProvider and camera selectors to created mocks + final AndroidCameraCameraX androidCameraCamerax = AndroidCameraCameraX(); + androidCameraCamerax.backCameraSelector = mockBackCameraSelector; + androidCameraCamerax.frontCameraSelector = mockFrontCameraSelector; + androidCameraCamerax.processCameraProvider = mockProcessCameraProvider; + + //Mock calls to native platform + when(mockProcessCameraProvider.getAvailableCameraInfos()).thenAnswer( + (_) async => [mockBackCameraInfo, mockFrontCameraInfo]); + when(mockBackCameraSelector.filter([mockFrontCameraInfo])) + .thenAnswer((_) async => []); + when(mockBackCameraSelector.filter([mockBackCameraInfo])) + .thenAnswer((_) async => [mockBackCameraInfo]); + when(mockFrontCameraSelector.filter([mockBackCameraInfo])) + .thenAnswer((_) async => []); + when(mockFrontCameraSelector.filter([mockFrontCameraInfo])) + .thenAnswer((_) async => [mockFrontCameraInfo]); + when(mockBackCameraInfo.getSensorRotationDegrees()) + .thenAnswer((_) async => 0); + when(mockFrontCameraInfo.getSensorRotationDegrees()) + .thenAnswer((_) async => 90); + + final List cameraDescriptions = + await androidCameraCamerax.availableCameras(); + + expect(cameraDescriptions.length, returnData.length); + for (int i = 0; i < returnData.length; i++) { + final Map typedData = + (returnData[i] as Map).cast(); + final CameraDescription cameraDescription = CameraDescription( + name: typedData['name']! as String, + lensDirection: (typedData['lensFacing']! as String) == 'front' + ? CameraLensDirection.front + : CameraLensDirection.back, + sensorOrientation: typedData['sensorOrientation']! as int, + ); + expect(cameraDescriptions[i], cameraDescription); + } + }); +} diff --git a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.mocks.dart b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.mocks.dart new file mode 100644 index 000000000000..44171eebda91 --- /dev/null +++ b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.mocks.dart @@ -0,0 +1,79 @@ +// Mocks generated by Mockito 5.3.2 from annotations +// in camera_android_camerax/test/android_camera_camerax_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i3; + +import 'package:camera_android_camerax/src/camera_info.dart' as _i4; +import 'package:camera_android_camerax/src/camera_selector.dart' as _i5; +import 'package:camera_android_camerax/src/process_camera_provider.dart' as _i2; +import 'package:mockito/mockito.dart' as _i1; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +/// A class which mocks [ProcessCameraProvider]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockProcessCameraProvider extends _i1.Mock + implements _i2.ProcessCameraProvider { + MockProcessCameraProvider() { + _i1.throwOnMissingStub(this); + } + + @override + _i3.Future> getAvailableCameraInfos() => + (super.noSuchMethod( + Invocation.method( + #getAvailableCameraInfos, + [], + ), + returnValue: _i3.Future>.value(<_i4.CameraInfo>[]), + ) as _i3.Future>); +} + +/// A class which mocks [CameraSelector]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockCameraSelector extends _i1.Mock implements _i5.CameraSelector { + MockCameraSelector() { + _i1.throwOnMissingStub(this); + } + + @override + _i3.Future> filter(List<_i4.CameraInfo>? cameraInfos) => + (super.noSuchMethod( + Invocation.method( + #filter, + [cameraInfos], + ), + returnValue: _i3.Future>.value(<_i4.CameraInfo>[]), + ) as _i3.Future>); +} + +/// A class which mocks [CameraInfo]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockCameraInfo extends _i1.Mock implements _i4.CameraInfo { + MockCameraInfo() { + _i1.throwOnMissingStub(this); + } + + @override + _i3.Future getSensorRotationDegrees() => (super.noSuchMethod( + Invocation.method( + #getSensorRotationDegrees, + [], + ), + returnValue: _i3.Future.value(0), + ) as _i3.Future); +} From 73986f4cc8570a72c4181a9b8557829d382e7201 Mon Sep 17 00:00:00 2001 From: Jakub Walusiak Date: Thu, 9 Feb 2023 03:19:47 +0100 Subject: [PATCH 091/130] [image_picker_android] Name picked files to match the original filenames where possible (#6096) * [image_picker_android] Name picked files to match the original filenames where possible. * [image_picker_android] Update CHANGELOG.md * [image_picker_android] Add license blocks * [image_picker_android] Clear imports, document FileUtils.getPathFromUri * [image_picker_android] Fix analysis issues --- .../image_picker_android/CHANGELOG.md | 3 +- .../plugins/imagepicker/FileUtils.java | 79 ++++++++++------ .../plugins/imagepicker/FileUtilTest.java | 66 ++++++++++++++ .../example/android/app/build.gradle | 2 + .../ImagePickerPickTest.java | 43 +++++++++ .../android/app/src/debug/AndroidManifest.xml | 12 +++ .../DriverExtensionActivity.java | 16 ++++ .../DummyContentProvider.java | 68 ++++++++++++++ .../app/src/main/res/raw/ic_launcher.png | Bin 0 -> 442 bytes .../example/lib/main.dart | 86 ++++++++++++------ .../image_picker_android/example/pubspec.yaml | 4 +- .../image_picker_android/pubspec.yaml | 2 +- 12 files changed, 317 insertions(+), 64 deletions(-) create mode 100644 packages/image_picker/image_picker_android/example/android/app/src/androidTest/java/io/flutter/plugins/imagepickerexample/ImagePickerPickTest.java create mode 100644 packages/image_picker/image_picker_android/example/android/app/src/main/java/io/flutter/plugins/imagepickerexample/DriverExtensionActivity.java create mode 100644 packages/image_picker/image_picker_android/example/android/app/src/main/java/io/flutter/plugins/imagepickerexample/DummyContentProvider.java create mode 100755 packages/image_picker/image_picker_android/example/android/app/src/main/res/raw/ic_launcher.png diff --git a/packages/image_picker/image_picker_android/CHANGELOG.md b/packages/image_picker/image_picker_android/CHANGELOG.md index 58c4951a2d4f..1ab21108d70f 100644 --- a/packages/image_picker/image_picker_android/CHANGELOG.md +++ b/packages/image_picker/image_picker_android/CHANGELOG.md @@ -1,6 +1,7 @@ -## NEXT +## 0.8.5+6 * Updates minimum Flutter version to 3.0. +* Fixes names of picked files to match original filenames where possible. ## 0.8.5+5 diff --git a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/FileUtils.java b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/FileUtils.java index 1f51a226c7e2..449480c19d9c 100644 --- a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/FileUtils.java +++ b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/FileUtils.java @@ -25,55 +25,60 @@ import android.content.ContentResolver; import android.content.Context; +import android.database.Cursor; import android.net.Uri; +import android.provider.MediaStore; import android.webkit.MimeTypeMap; +import io.flutter.Log; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.UUID; class FileUtils { - + /** + * Copies the file from the given content URI to a temporary directory, retaining the original + * file name if possible. + * + *

Each file is placed in its own directory to avoid conflicts according to the following + * scheme: {cacheDir}/{randomUuid}/{fileName} + * + *

If the original file name is unknown, a predefined "image_picker" filename is used and the + * file extension is deduced from the mime type (with fallback to ".jpg" in case of failure). + */ String getPathFromUri(final Context context, final Uri uri) { - File file = null; - InputStream inputStream = null; - OutputStream outputStream = null; - boolean success = false; - try { - String extension = getImageExtension(context, uri); - inputStream = context.getContentResolver().openInputStream(uri); - file = File.createTempFile("image_picker", extension, context.getCacheDir()); - file.deleteOnExit(); - outputStream = new FileOutputStream(file); - if (inputStream != null) { - copy(inputStream, outputStream); - success = true; + try (InputStream inputStream = context.getContentResolver().openInputStream(uri)) { + String uuid = UUID.randomUUID().toString(); + File targetDirectory = new File(context.getCacheDir(), uuid); + targetDirectory.mkdir(); + // TODO(SynSzakala) according to the docs, `deleteOnExit` does not work reliably on Android; we should preferably + // just clear the picked files after the app startup. + targetDirectory.deleteOnExit(); + String fileName = getImageName(context, uri); + if (fileName == null) { + Log.w("FileUtils", "Cannot get file name for " + uri); + fileName = "image_picker" + getImageExtension(context, uri); } - } catch (IOException ignored) { - } finally { - try { - if (inputStream != null) inputStream.close(); - } catch (IOException ignored) { - } - try { - if (outputStream != null) outputStream.close(); - } catch (IOException ignored) { - // If closing the output stream fails, we cannot be sure that the - // target file was written in full. Flushing the stream merely moves - // the bytes into the OS, not necessarily to the file. - success = false; + File file = new File(targetDirectory, fileName); + try (OutputStream outputStream = new FileOutputStream(file)) { + copy(inputStream, outputStream); + return file.getPath(); } + } catch (IOException e) { + // If closing the output stream fails, we cannot be sure that the + // target file was written in full. Flushing the stream merely moves + // the bytes into the OS, not necessarily to the file. + return null; } - return success ? file.getPath() : null; } /** @return extension of image with dot, or default .jpg if it none. */ private static String getImageExtension(Context context, Uri uriImage) { - String extension = null; + String extension; try { - String imagePath = uriImage.getPath(); if (uriImage.getScheme().equals(ContentResolver.SCHEME_CONTENT)) { final MimeTypeMap mime = MimeTypeMap.getSingleton(); extension = mime.getExtensionFromMimeType(context.getContentResolver().getType(uriImage)); @@ -94,6 +99,20 @@ private static String getImageExtension(Context context, Uri uriImage) { return "." + extension; } + /** @return name of the image provided by ContentResolver; this may be null. */ + private static String getImageName(Context context, Uri uriImage) { + try (Cursor cursor = queryImageName(context, uriImage)) { + if (cursor == null || !cursor.moveToFirst() || cursor.getColumnCount() < 1) return null; + return cursor.getString(0); + } + } + + private static Cursor queryImageName(Context context, Uri uriImage) { + return context + .getContentResolver() + .query(uriImage, new String[] {MediaStore.MediaColumns.DISPLAY_NAME}, null, null, null); + } + private static void copy(InputStream in, OutputStream out) throws IOException { final byte[] buffer = new byte[4 * 1024]; int bytesRead; diff --git a/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/FileUtilTest.java b/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/FileUtilTest.java index 32e3ebc6183d..0ea0173fa954 100644 --- a/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/FileUtilTest.java +++ b/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/FileUtilTest.java @@ -8,8 +8,15 @@ import static org.junit.Assert.assertTrue; import static org.robolectric.Shadows.shadowOf; +import android.content.ContentProvider; +import android.content.ContentValues; import android.content.Context; +import android.database.Cursor; +import android.database.MatrixCursor; import android.net.Uri; +import android.provider.MediaStore; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.test.core.app.ApplicationProvider; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; @@ -19,6 +26,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; import org.robolectric.shadows.ShadowContentResolver; @@ -63,4 +71,62 @@ public void FileUtil_getImageExtension() throws IOException { String path = fileUtils.getPathFromUri(context, uri); assertTrue(path.endsWith(".jpg")); } + + @Test + public void FileUtil_getImageName() throws IOException { + Uri uri = Uri.parse("content://dummy/dummy.png"); + Robolectric.buildContentProvider(MockContentProvider.class).create("dummy"); + shadowContentResolver.registerInputStream( + uri, new ByteArrayInputStream("imageStream".getBytes(UTF_8))); + String path = fileUtils.getPathFromUri(context, uri); + assertTrue(path.endsWith("dummy.png")); + } + + private static class MockContentProvider extends ContentProvider { + + @Override + public boolean onCreate() { + return true; + } + + @Nullable + @Override + public Cursor query( + @NonNull Uri uri, + @Nullable String[] projection, + @Nullable String selection, + @Nullable String[] selectionArgs, + @Nullable String sortOrder) { + MatrixCursor cursor = new MatrixCursor(new String[] {MediaStore.MediaColumns.DISPLAY_NAME}); + cursor.addRow(new Object[] {"dummy.png"}); + return cursor; + } + + @Nullable + @Override + public String getType(@NonNull Uri uri) { + return "image/png"; + } + + @Nullable + @Override + public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) { + return null; + } + + @Override + public int delete( + @NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) { + return 0; + } + + @Override + public int update( + @NonNull Uri uri, + @Nullable ContentValues values, + @Nullable String selection, + @Nullable String[] selectionArgs) { + return 0; + } + } } diff --git a/packages/image_picker/image_picker_android/example/android/app/build.gradle b/packages/image_picker/image_picker_android/example/android/app/build.gradle index 31d8c82a0a9d..f8487c7959f1 100755 --- a/packages/image_picker/image_picker_android/example/android/app/build.gradle +++ b/packages/image_picker/image_picker_android/example/android/app/build.gradle @@ -63,5 +63,7 @@ dependencies { testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' + implementation project(':image_picker_android') + implementation project(':espresso') api 'androidx.test:core:1.4.0' } diff --git a/packages/image_picker/image_picker_android/example/android/app/src/androidTest/java/io/flutter/plugins/imagepickerexample/ImagePickerPickTest.java b/packages/image_picker/image_picker_android/example/android/app/src/androidTest/java/io/flutter/plugins/imagepickerexample/ImagePickerPickTest.java new file mode 100644 index 000000000000..8b7ae11d5c2d --- /dev/null +++ b/packages/image_picker/image_picker_android/example/android/app/src/androidTest/java/io/flutter/plugins/imagepickerexample/ImagePickerPickTest.java @@ -0,0 +1,43 @@ +// Copyright 2013 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.imagepickerexample; + +import static androidx.test.espresso.flutter.EspressoFlutter.onFlutterWidget; +import static androidx.test.espresso.flutter.action.FlutterActions.click; +import static androidx.test.espresso.flutter.assertion.FlutterAssertions.matches; +import static androidx.test.espresso.flutter.matcher.FlutterMatchers.withText; +import static androidx.test.espresso.flutter.matcher.FlutterMatchers.withValueKey; +import static androidx.test.espresso.intent.Intents.intended; +import static androidx.test.espresso.intent.Intents.intending; +import static androidx.test.espresso.intent.matcher.IntentMatchers.hasAction; + +import android.app.Activity; +import android.app.Instrumentation; +import android.content.Intent; +import android.net.Uri; +import androidx.test.espresso.intent.rule.IntentsTestRule; +import org.junit.Ignore; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; + +public class ImagePickerPickTest { + + @Rule public TestRule rule = new IntentsTestRule<>(DriverExtensionActivity.class); + + @Test + @Ignore("Doesn't run in Firebase Test Lab: https://github.com/flutter/flutter/issues/94748") + public void imageIsPickedWithOriginalName() { + Instrumentation.ActivityResult result = + new Instrumentation.ActivityResult( + Activity.RESULT_OK, new Intent().setData(Uri.parse("content://dummy/dummy.png"))); + intending(hasAction(Intent.ACTION_GET_CONTENT)).respondWith(result); + onFlutterWidget(withValueKey("image_picker_example_from_gallery")).perform(click()); + onFlutterWidget(withText("PICK")).perform(click()); + intended(hasAction(Intent.ACTION_GET_CONTENT)); + onFlutterWidget(withValueKey("image_picker_example_picked_image_name")) + .check(matches(withText("dummy.png"))); + } +} diff --git a/packages/image_picker/image_picker_android/example/android/app/src/debug/AndroidManifest.xml b/packages/image_picker/image_picker_android/example/android/app/src/debug/AndroidManifest.xml index 6f85cefded34..317af1d1a371 100644 --- a/packages/image_picker/image_picker_android/example/android/app/src/debug/AndroidManifest.xml +++ b/packages/image_picker/image_picker_android/example/android/app/src/debug/AndroidManifest.xml @@ -13,5 +13,17 @@ android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize"> + + + diff --git a/packages/image_picker/image_picker_android/example/android/app/src/main/java/io/flutter/plugins/imagepickerexample/DriverExtensionActivity.java b/packages/image_picker/image_picker_android/example/android/app/src/main/java/io/flutter/plugins/imagepickerexample/DriverExtensionActivity.java new file mode 100644 index 000000000000..b35a6c4b0e49 --- /dev/null +++ b/packages/image_picker/image_picker_android/example/android/app/src/main/java/io/flutter/plugins/imagepickerexample/DriverExtensionActivity.java @@ -0,0 +1,16 @@ +// Copyright 2013 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.imagepickerexample; + +import androidx.annotation.NonNull; +import io.flutter.embedding.android.FlutterActivity; + +public class DriverExtensionActivity extends FlutterActivity { + @NonNull + @Override + public String getDartEntrypointFunctionName() { + return "appMain"; + } +} diff --git a/packages/image_picker/image_picker_android/example/android/app/src/main/java/io/flutter/plugins/imagepickerexample/DummyContentProvider.java b/packages/image_picker/image_picker_android/example/android/app/src/main/java/io/flutter/plugins/imagepickerexample/DummyContentProvider.java new file mode 100644 index 000000000000..8967318ee977 --- /dev/null +++ b/packages/image_picker/image_picker_android/example/android/app/src/main/java/io/flutter/plugins/imagepickerexample/DummyContentProvider.java @@ -0,0 +1,68 @@ +// Copyright 2013 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.imagepickerexample; + +import android.content.ContentProvider; +import android.content.ContentValues; +import android.content.res.AssetFileDescriptor; +import android.database.Cursor; +import android.database.MatrixCursor; +import android.net.Uri; +import android.provider.MediaStore; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +public class DummyContentProvider extends ContentProvider { + @Override + public boolean onCreate() { + return true; + } + + @Nullable + @Override + public AssetFileDescriptor openAssetFile(@NonNull Uri uri, @NonNull String mode) { + return getContext().getResources().openRawResourceFd(R.raw.ic_launcher); + } + + @Nullable + @Override + public Cursor query( + @NonNull Uri uri, + @Nullable String[] projection, + @Nullable String selection, + @Nullable String[] selectionArgs, + @Nullable String sortOrder) { + MatrixCursor cursor = new MatrixCursor(new String[] {MediaStore.MediaColumns.DISPLAY_NAME}); + cursor.addRow(new Object[] {"dummy.png"}); + return cursor; + } + + @Nullable + @Override + public String getType(@NonNull Uri uri) { + return "image/png"; + } + + @Nullable + @Override + public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) { + return null; + } + + @Override + public int delete( + @NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) { + return 0; + } + + @Override + public int update( + @NonNull Uri uri, + @Nullable ContentValues values, + @Nullable String selection, + @Nullable String[] selectionArgs) { + return 0; + } +} diff --git a/packages/image_picker/image_picker_android/example/android/app/src/main/res/raw/ic_launcher.png b/packages/image_picker/image_picker_android/example/android/app/src/main/res/raw/ic_launcher.png new file mode 100755 index 0000000000000000000000000000000000000000..17987b79bb8a35cc66c3c1fd44f5a5526c1b78be GIT binary patch literal 442 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5Xx&nMcT!A!W`0S9QKQy;}1Cl^CgaH=;G9cpY;r$Q>i*pfB zP2drbID<_#qf;rPZx^FqH)F_D#*k@@q03KywUtLX8Ua?`H+NMzkczFPK3lFz@i_kW%1NOn0|D2I9n9wzH8m|-tHjsw|9>@K=iMBhxvkv6m8Y-l zytQ?X=U+MF$@3 zt`~i=@j|6y)RWMK--}M|=T`o&^Ni>IoWKHEbBXz7?A@mgWoL>!*SXo`SZH-*HSdS+ yn*9;$7;m`l>wYBC5bq;=U}IMqLzqbYCidGC!)_gkIk_C@U { } } - Future _onImageButtonPressed(ImageSource source, - {BuildContext? context, bool isMultiImage = false}) async { + Future _onImageButtonPressed( + BuildContext context, { + required ImageSource source, + bool isMultiImage = false, + }) async { if (_controller != null) { await _controller!.setVolume(0.0); } if (isVideo) { final XFile? file = await _picker.getVideo( source: source, maxDuration: const Duration(seconds: 10)); + if (file != null && context.mounted) { + _showPickedSnackBar(context, [file]); + } await _playVideo(file); - } else if (isMultiImage) { - await _displayPickImageDialog(context!, + } else if (isMultiImage && context.mounted) { + await _displayPickImageDialog(context, (double? maxWidth, double? maxHeight, int? quality) async { try { final List? pickedFileList = await _picker.getMultiImage( @@ -98,17 +110,16 @@ class _MyHomePageState extends State { maxHeight: maxHeight, imageQuality: quality, ); - setState(() { - _imageFileList = pickedFileList; - }); + if (pickedFileList != null && context.mounted) { + _showPickedSnackBar(context, pickedFileList); + } + setState(() => _imageFileList = pickedFileList); } catch (e) { - setState(() { - _pickImageError = e; - }); + setState(() => _pickImageError = e); } }); } else { - await _displayPickImageDialog(context!, + await _displayPickImageDialog(context, (double? maxWidth, double? maxHeight, int? quality) async { try { final XFile? pickedFile = await _picker.getImage( @@ -117,13 +128,12 @@ class _MyHomePageState extends State { maxHeight: maxHeight, imageQuality: quality, ); - setState(() { - _setImageFileListFromFile(pickedFile); - }); + if (pickedFile != null && context.mounted) { + _showPickedSnackBar(context, [pickedFile]); + } + setState(() => _setImageFileListFromFile(pickedFile)); } catch (e) { - setState(() { - _pickImageError = e; - }); + setState(() => _pickImageError = e); } }); } @@ -183,13 +193,21 @@ class _MyHomePageState extends State { child: ListView.builder( key: UniqueKey(), itemBuilder: (BuildContext context, int index) { - // Why network for web? - // See https://pub.dev/packages/image_picker#getting-ready-for-the-web-platform - return Semantics( - label: 'image_picker_example_picked_image', - child: kIsWeb - ? Image.network(_imageFileList![index].path) - : Image.file(File(_imageFileList![index].path)), + final XFile image = _imageFileList![index]; + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text(image.name, + key: const Key('image_picker_example_picked_image_name')), + // Why network for web? + // See https://pub.dev/packages/image_picker#getting-ready-for-the-web-platform + Semantics( + label: 'image_picker_example_picked_image', + child: kIsWeb + ? Image.network(image.path) + : Image.file(File(image.path)), + ), + ], ); }, itemCount: _imageFileList!.length, @@ -283,9 +301,10 @@ class _MyHomePageState extends State { Semantics( label: 'image_picker_example_from_gallery', child: FloatingActionButton( + key: const Key('image_picker_example_from_gallery'), onPressed: () { isVideo = false; - _onImageButtonPressed(ImageSource.gallery, context: context); + _onImageButtonPressed(context, source: ImageSource.gallery); }, heroTag: 'image0', tooltip: 'Pick Image from gallery', @@ -298,8 +317,8 @@ class _MyHomePageState extends State { onPressed: () { isVideo = false; _onImageButtonPressed( - ImageSource.gallery, - context: context, + context, + source: ImageSource.gallery, isMultiImage: true, ); }, @@ -313,7 +332,7 @@ class _MyHomePageState extends State { child: FloatingActionButton( onPressed: () { isVideo = false; - _onImageButtonPressed(ImageSource.camera, context: context); + _onImageButtonPressed(context, source: ImageSource.camera); }, heroTag: 'image2', tooltip: 'Take a Photo', @@ -326,7 +345,7 @@ class _MyHomePageState extends State { backgroundColor: Colors.red, onPressed: () { isVideo = true; - _onImageButtonPressed(ImageSource.gallery); + _onImageButtonPressed(context, source: ImageSource.gallery); }, heroTag: 'video0', tooltip: 'Pick Video from gallery', @@ -339,7 +358,7 @@ class _MyHomePageState extends State { backgroundColor: Colors.red, onPressed: () { isVideo = true; - _onImageButtonPressed(ImageSource.camera); + _onImageButtonPressed(context, source: ImageSource.camera); }, heroTag: 'video1', tooltip: 'Take a Video', @@ -417,6 +436,13 @@ class _MyHomePageState extends State { ); }); } + + void _showPickedSnackBar(BuildContext context, List files) { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text('Picked: ${files.map((XFile it) => it.name).join(',')}'), + duration: const Duration(seconds: 2), + )); + } } typedef OnPickImageCallback = void Function( diff --git a/packages/image_picker/image_picker_android/example/pubspec.yaml b/packages/image_picker/image_picker_android/example/pubspec.yaml index bfcdbad511ae..bfeac3de14d5 100755 --- a/packages/image_picker/image_picker_android/example/pubspec.yaml +++ b/packages/image_picker/image_picker_android/example/pubspec.yaml @@ -9,6 +9,8 @@ environment: dependencies: flutter: sdk: flutter + flutter_driver: + sdk: flutter flutter_plugin_android_lifecycle: ^2.0.1 image_picker_android: # When depending on this package from a real application you should use: @@ -22,8 +24,6 @@ dependencies: dev_dependencies: espresso: ^0.2.0 - flutter_driver: - sdk: flutter flutter_test: sdk: flutter integration_test: diff --git a/packages/image_picker/image_picker_android/pubspec.yaml b/packages/image_picker/image_picker_android/pubspec.yaml index 7aa1a2258645..a0516685964c 100755 --- a/packages/image_picker/image_picker_android/pubspec.yaml +++ b/packages/image_picker/image_picker_android/pubspec.yaml @@ -2,7 +2,7 @@ name: image_picker_android description: Android implementation of the image_picker plugin. repository: https://github.com/flutter/plugins/tree/main/packages/image_picker/image_picker_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+image_picker%22 -version: 0.8.5+5 +version: 0.8.5+6 environment: sdk: ">=2.14.0 <3.0.0" From 2daa072757a6437ea1690dfe3599c4bfcc12e842 Mon Sep 17 00:00:00 2001 From: Drew Roen <102626803+drewroengoogle@users.noreply.github.com> Date: Thu, 9 Feb 2023 13:23:54 -0600 Subject: [PATCH 092/130] Update .cirrus.yml (#7134) * Update .cirrus.yml * Restart checks --- .cirrus.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.cirrus.yml b/.cirrus.yml index 8d659c3d72aa..2490255dc17c 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -1,4 +1,4 @@ -gcp_credentials: ENCRYPTED[!9e38557f08108136b3625b7e62c64cc9eccc50365ffeaaa55c6be52f1d8fd6225af5badc69983ca08484274f02f34424!] +gcp_credentials: ENCRYPTED[!3a93d98d7c95a41f5033834ef30e50928fc5d81239dc632b153c2628200a8187f3811cb01ce338b1ab3b6505a7a65c37!] # Run on PRs and main branch post submit only. Don't run tests when tagging. only_if: $CIRRUS_TAG == '' && ($CIRRUS_PR != '' || $CIRRUS_BRANCH == 'main') From b3c7582da4ba09af786ae95b1305d207f578a80f Mon Sep 17 00:00:00 2001 From: Todd Volkert Date: Thu, 9 Feb 2023 16:27:06 -0800 Subject: [PATCH 093/130] [google_sign_in] Slight cleanup in GoogleSignInPlugin (#7013) * Slight cleanup in GoogleSignInPlugin 1) Use switch instead of if/else and show all possible states 2) Handle the case of synchronous failure in signInSilently() by checking isComplete() instead of isSuccessful() * Run formatter and fix some compilation errors * Version bump * Declare throws * Update error text expectation * Diagnose failing test * Code * Code * Fix test --- .../google_sign_in_android/CHANGELOG.md | 3 +- .../googlesignin/GoogleSignInPlugin.java | 28 +++++++++++-------- .../googlesignin/GoogleSignInTest.java | 26 +++++++++++++++++ .../google_sign_in_android/pubspec.yaml | 2 +- 4 files changed, 46 insertions(+), 13 deletions(-) diff --git a/packages/google_sign_in/google_sign_in_android/CHANGELOG.md b/packages/google_sign_in/google_sign_in_android/CHANGELOG.md index 5d1b2d24d7d1..6ce3cb97e8db 100644 --- a/packages/google_sign_in/google_sign_in_android/CHANGELOG.md +++ b/packages/google_sign_in/google_sign_in_android/CHANGELOG.md @@ -1,5 +1,6 @@ -## NEXT +## 6.1.6 +* Minor implementation cleanup * Updates minimum Flutter version to 3.0. ## 6.1.5 diff --git a/packages/google_sign_in/google_sign_in_android/android/src/main/java/io/flutter/plugins/googlesignin/GoogleSignInPlugin.java b/packages/google_sign_in/google_sign_in_android/android/src/main/java/io/flutter/plugins/googlesignin/GoogleSignInPlugin.java index d345d4976c63..8963a5169e89 100644 --- a/packages/google_sign_in/google_sign_in_android/android/src/main/java/io/flutter/plugins/googlesignin/GoogleSignInPlugin.java +++ b/packages/google_sign_in/google_sign_in_android/android/src/main/java/io/flutter/plugins/googlesignin/GoogleSignInPlugin.java @@ -404,9 +404,9 @@ public void init( public void signInSilently(Result result) { checkAndSetPendingOperation(METHOD_SIGN_IN_SILENTLY, result); Task task = signInClient.silentSignIn(); - if (task.isSuccessful()) { + if (task.isComplete()) { // There's immediate result available. - onSignInAccount(task.getResult()); + onSignInResult(task); } else { task.addOnCompleteListener( new OnCompleteListener() { @@ -516,7 +516,7 @@ private void onSignInResult(Task completedTask) { GoogleSignInAccount account = completedTask.getResult(ApiException.class); onSignInAccount(account); } catch (ApiException e) { - // Forward all errors and let Dart side decide how to handle. + // Forward all errors and let Dart decide how to handle. String errorCode = errorCodeForStatus(e.getStatusCode()); finishWithError(errorCode, e.toString()); } catch (RuntimeExecutionException e) { @@ -538,14 +538,20 @@ private void onSignInAccount(GoogleSignInAccount account) { } private String errorCodeForStatus(int statusCode) { - if (statusCode == GoogleSignInStatusCodes.SIGN_IN_CANCELLED) { - return ERROR_REASON_SIGN_IN_CANCELED; - } else if (statusCode == CommonStatusCodes.SIGN_IN_REQUIRED) { - return ERROR_REASON_SIGN_IN_REQUIRED; - } else if (statusCode == CommonStatusCodes.NETWORK_ERROR) { - return ERROR_REASON_NETWORK_ERROR; - } else { - return ERROR_REASON_SIGN_IN_FAILED; + switch (statusCode) { + case GoogleSignInStatusCodes.SIGN_IN_CANCELLED: + return ERROR_REASON_SIGN_IN_CANCELED; + case CommonStatusCodes.SIGN_IN_REQUIRED: + return ERROR_REASON_SIGN_IN_REQUIRED; + case CommonStatusCodes.NETWORK_ERROR: + return ERROR_REASON_NETWORK_ERROR; + case GoogleSignInStatusCodes.SIGN_IN_CURRENTLY_IN_PROGRESS: + case GoogleSignInStatusCodes.SIGN_IN_FAILED: + case CommonStatusCodes.INVALID_ACCOUNT: + case CommonStatusCodes.INTERNAL_ERROR: + return ERROR_REASON_SIGN_IN_FAILED; + default: + return ERROR_REASON_SIGN_IN_FAILED; } } diff --git a/packages/google_sign_in/google_sign_in_android/android/src/test/java/io/flutter/plugins/googlesignin/GoogleSignInTest.java b/packages/google_sign_in/google_sign_in_android/android/src/test/java/io/flutter/plugins/googlesignin/GoogleSignInTest.java index 9692417390a5..78568460c9e6 100644 --- a/packages/google_sign_in/google_sign_in_android/android/src/test/java/io/flutter/plugins/googlesignin/GoogleSignInTest.java +++ b/packages/google_sign_in/google_sign_in_android/android/src/test/java/io/flutter/plugins/googlesignin/GoogleSignInTest.java @@ -17,7 +17,11 @@ import com.google.android.gms.auth.api.signin.GoogleSignInAccount; import com.google.android.gms.auth.api.signin.GoogleSignInClient; import com.google.android.gms.auth.api.signin.GoogleSignInOptions; +import com.google.android.gms.common.api.ApiException; +import com.google.android.gms.common.api.CommonStatusCodes; import com.google.android.gms.common.api.Scope; +import com.google.android.gms.common.api.Status; +import com.google.android.gms.tasks.Task; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; @@ -43,6 +47,7 @@ public class GoogleSignInTest { @Mock GoogleSignInWrapper mockGoogleSignIn; @Mock GoogleSignInAccount account; @Mock GoogleSignInClient mockClient; + @Mock Task mockSignInTask; private GoogleSignInPlugin plugin; @Before @@ -204,6 +209,27 @@ public void signInThrowsWithoutActivity() { plugin.onMethodCall(new MethodCall("signIn", null), null); } + @Test + public void signInSilentlyThatImmediatelyCompletesWithoutResultFinishesWithError() + throws ApiException { + final String clientId = "fakeClientId"; + MethodCall methodCall = buildInitMethodCall(clientId, null); + initAndAssertServerClientId(methodCall, clientId); + + ApiException exception = + new ApiException(new Status(CommonStatusCodes.SIGN_IN_REQUIRED, "Error text")); + when(mockClient.silentSignIn()).thenReturn(mockSignInTask); + when(mockSignInTask.isComplete()).thenReturn(true); + when(mockSignInTask.getResult(ApiException.class)).thenThrow(exception); + + plugin.onMethodCall(new MethodCall("signInSilently", null), result); + verify(result) + .error( + "sign_in_required", + "com.google.android.gms.common.api.ApiException: 4: Error text", + null); + } + @Test public void init_LoadsServerClientIdFromResources() { final String packageName = "fakePackageName"; diff --git a/packages/google_sign_in/google_sign_in_android/pubspec.yaml b/packages/google_sign_in/google_sign_in_android/pubspec.yaml index b6b581a75764..4be89f27286a 100644 --- a/packages/google_sign_in/google_sign_in_android/pubspec.yaml +++ b/packages/google_sign_in/google_sign_in_android/pubspec.yaml @@ -2,7 +2,7 @@ name: google_sign_in_android description: Android implementation of the google_sign_in plugin. repository: https://github.com/flutter/plugins/tree/main/packages/google_sign_in/google_sign_in_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+google_sign_in%22 -version: 6.1.5 +version: 6.1.6 environment: sdk: ">=2.14.0 <3.0.0" From 02571ec0dd36190fb38433a06b6316f2a6b08a54 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Thu, 9 Feb 2023 20:36:50 -0500 Subject: [PATCH 094/130] Manual roll Flutter from 2303f42250b1 to e3471f08d1d3 (24 revisions) (#7147) * 4a9660881 Reland "Stop recursively including assets from asset directories" (flutter/flutter#120312) * 4ddf0a89e Manual roll Flutter Engine from cc4ca6a06ab3 to 89c8a1393d4b (6 revisions) (flutter/flutter#120319) * b4908f376 Update .cirrus.yml (flutter/flutter#120315) * ef854a3db [Tool] [Windows] Output build duration (flutter/flutter#120311) * 0fb4406c3 Revert "[web] Move JS content to its own `.js` files (#117691)" (flutter/flutter#120275) * b1c4d5686 Fix widget inspector null check (flutter/flutter#120143) * dee226ef8 Manual roll Flutter Engine from 89c8a1393d4b to 2f2e2e27cb28 (3 revisions) (flutter/flutter#120333) * 0e7c5a885 Roll Plugins from f59c08db3f27 to 73986f4cc857 (5 revisions) (flutter/flutter#120362) * 468e21c50 Manual roll Flutter Engine from 2f2e2e27cb28 to eb346ba63f69 (7 revisions) (flutter/flutter#120364) * cd3806337 Update gallery.dart (flutter/flutter#120366) * 999612674 Add proper disabled values for input chips (flutter/flutter#120192) * 5e506aeb6 Add missing parameters to `SwitchListTile` (flutter/flutter#120115) * 0521c60cd Support --local-engine=ios_debug_sim (flutter/flutter#119524) * 42b20cf95 Added ListTile.titleAlignment, ListTileThemeData.titleAlignment (flutter/flutter#119872) * 1546fa08d [flutter_tools] toolExit on sdkmanager exit during doctor --android-licenses (flutter/flutter#120330) * 3fdd6ee46 Reland "Overlay always applies clip (#113770)" (flutter/flutter#116674) * c8c862141 Clean up null safety messages (flutter/flutter#120350) * 91dc513a3 Add missing parameters to `CheckboxListTile` (flutter/flutter#120118) * 1faa95009 Roll Flutter Engine from eb346ba63f69 to 603fd71f4749 (2 revisions) (flutter/flutter#120381) * 2239f6c8a Roll Flutter Engine from 603fd71f4749 to 39c41c40a4bc (3 revisions) (flutter/flutter#120393) * 425ab5dca Remove test that verifies we can switch to stateless (flutter/flutter#120390) * fecd5c969 Roll Flutter Engine from 39c41c40a4bc to 40e17fb5244c (3 revisions) (flutter/flutter#120397) * f945ad99c Resolve dwarf paths to enable source-code mapping of stacktraces (flutter/flutter#114767) * e3471f08d Roll Flutter Engine from 40e17fb5244c to 9a40a384997d (3 revisions) (flutter/flutter#120403) --- .ci/flutter_master.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/flutter_master.version b/.ci/flutter_master.version index ee2466f94359..cb3f9ddec296 100644 --- a/.ci/flutter_master.version +++ b/.ci/flutter_master.version @@ -1 +1 @@ -2303f42250b11cd4e553660d89483ead57bc14d1 +e3471f08d1d3f816fff8afd1ce9385dd3feb73d2 From f47f740510f6226aebb79d6b05bc44c4364dfe6f Mon Sep 17 00:00:00 2001 From: stuartmorgan Date: Fri, 10 Feb 2023 13:09:49 -0800 Subject: [PATCH 095/130] [file_selector] Relax xdg_directories constraint (#7157) `xdg_directories` 0.2.0 was update to 1.0 to reflect that it has a stable API, with no other changes. This updates the constraint to allow either 0.2.x or 1.x to avoid dependency conflicts in the future. --- packages/path_provider/path_provider_linux/CHANGELOG.md | 3 ++- packages/path_provider/path_provider_linux/pubspec.yaml | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/path_provider/path_provider_linux/CHANGELOG.md b/packages/path_provider/path_provider_linux/CHANGELOG.md index d18e565f83f4..fa37eec3013b 100644 --- a/packages/path_provider/path_provider_linux/CHANGELOG.md +++ b/packages/path_provider/path_provider_linux/CHANGELOG.md @@ -1,5 +1,6 @@ -## NEXT +## 2.1.8 +* Adds compatibility with `xdg_directories` 1.0. * Updates minimum Flutter version to 3.0. ## 2.1.7 diff --git a/packages/path_provider/path_provider_linux/pubspec.yaml b/packages/path_provider/path_provider_linux/pubspec.yaml index b0d86fed090e..ecb9ea67525e 100644 --- a/packages/path_provider/path_provider_linux/pubspec.yaml +++ b/packages/path_provider/path_provider_linux/pubspec.yaml @@ -2,7 +2,7 @@ name: path_provider_linux description: Linux implementation of the path_provider plugin repository: https://github.com/flutter/plugins/tree/main/packages/path_provider/path_provider_linux issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+path_provider%22 -version: 2.1.7 +version: 2.1.8 environment: sdk: ">=2.12.0 <3.0.0" @@ -21,7 +21,7 @@ dependencies: sdk: flutter path: ^1.8.0 path_provider_platform_interface: ^2.0.0 - xdg_directories: ^0.2.0 + xdg_directories: ">=0.2.0 <2.0.0" dev_dependencies: flutter_test: From f3bc6f1eb0c22d95c7dab275c54a057d491cf1c5 Mon Sep 17 00:00:00 2001 From: stuartmorgan Date: Fri, 10 Feb 2023 19:35:05 -0800 Subject: [PATCH 096/130] [various] Raise JVM limit in example builds (#7155) Speculative fix for the intermittent OOM build errors. --- packages/camera/camera/example/android/gradle.properties | 2 +- .../camera/camera_android/example/android/gradle.properties | 2 +- .../camera_android_camerax/example/android/gradle.properties | 2 +- packages/espresso/example/android/gradle.properties | 2 +- .../example/android/gradle.properties | 2 +- .../google_maps_flutter/example/android/gradle.properties | 2 +- .../example/android/gradle.properties | 2 +- .../google_sign_in/example/android/gradle.properties | 2 +- .../google_sign_in_android/example/android/gradle.properties | 2 +- .../image_picker/image_picker/example/android/gradle.properties | 2 +- .../image_picker_android/example/android/gradle.properties | 2 +- .../in_app_purchase/example/android/gradle.properties | 2 +- .../in_app_purchase_android/example/android/gradle.properties | 2 +- .../local_auth/local_auth/example/android/gradle.properties | 2 +- .../local_auth_android/example/android/gradle.properties | 2 +- .../path_provider/example/android/gradle.properties | 2 +- .../path_provider_android/example/android/gradle.properties | 2 +- .../quick_actions/example/android/gradle.properties | 2 +- .../quick_actions_android/example/android/gradle.properties | 2 +- .../shared_preferences/example/android/gradle.properties | 2 +- .../example/android/gradle.properties | 2 +- .../url_launcher/url_launcher/example/android/gradle.properties | 2 +- .../url_launcher_android/example/android/gradle.properties | 2 +- .../video_player/video_player/example/android/gradle.properties | 2 +- .../video_player_android/example/android/gradle.properties | 2 +- .../webview_flutter/example/android/gradle.properties | 2 +- .../webview_flutter_android/example/android/gradle.properties | 2 +- 27 files changed, 27 insertions(+), 27 deletions(-) diff --git a/packages/camera/camera/example/android/gradle.properties b/packages/camera/camera/example/android/gradle.properties index b253d8e5f746..d0448f163e41 100644 --- a/packages/camera/camera/example/android/gradle.properties +++ b/packages/camera/camera/example/android/gradle.properties @@ -1,4 +1,4 @@ -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx4G android.useAndroidX=true android.enableJetifier=false android.enableR8=true diff --git a/packages/camera/camera_android/example/android/gradle.properties b/packages/camera/camera_android/example/android/gradle.properties index b253d8e5f746..d0448f163e41 100644 --- a/packages/camera/camera_android/example/android/gradle.properties +++ b/packages/camera/camera_android/example/android/gradle.properties @@ -1,4 +1,4 @@ -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx4G android.useAndroidX=true android.enableJetifier=false android.enableR8=true diff --git a/packages/camera/camera_android_camerax/example/android/gradle.properties b/packages/camera/camera_android_camerax/example/android/gradle.properties index 94adc3a3f97a..598d13fee446 100644 --- a/packages/camera/camera_android_camerax/example/android/gradle.properties +++ b/packages/camera/camera_android_camerax/example/android/gradle.properties @@ -1,3 +1,3 @@ -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx4G android.useAndroidX=true android.enableJetifier=true diff --git a/packages/espresso/example/android/gradle.properties b/packages/espresso/example/android/gradle.properties index 38c8d4544ff1..2f3603c9ff62 100644 --- a/packages/espresso/example/android/gradle.properties +++ b/packages/espresso/example/android/gradle.properties @@ -1,4 +1,4 @@ -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx4G android.enableR8=true android.useAndroidX=true android.enableJetifier=true diff --git a/packages/flutter_plugin_android_lifecycle/example/android/gradle.properties b/packages/flutter_plugin_android_lifecycle/example/android/gradle.properties index 38c8d4544ff1..2f3603c9ff62 100644 --- a/packages/flutter_plugin_android_lifecycle/example/android/gradle.properties +++ b/packages/flutter_plugin_android_lifecycle/example/android/gradle.properties @@ -1,4 +1,4 @@ -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx4G android.enableR8=true android.useAndroidX=true android.enableJetifier=true diff --git a/packages/google_maps_flutter/google_maps_flutter/example/android/gradle.properties b/packages/google_maps_flutter/google_maps_flutter/example/android/gradle.properties index 207beb63fb48..c6c9db00b996 100644 --- a/packages/google_maps_flutter/google_maps_flutter/example/android/gradle.properties +++ b/packages/google_maps_flutter/google_maps_flutter/example/android/gradle.properties @@ -1,4 +1,4 @@ -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx4G android.enableR8=true android.useAndroidX=true android.enableJetifier=true diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/android/gradle.properties b/packages/google_maps_flutter/google_maps_flutter_android/example/android/gradle.properties index 207beb63fb48..c6c9db00b996 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/example/android/gradle.properties +++ b/packages/google_maps_flutter/google_maps_flutter_android/example/android/gradle.properties @@ -1,4 +1,4 @@ -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx4G android.enableR8=true android.useAndroidX=true android.enableJetifier=true diff --git a/packages/google_sign_in/google_sign_in/example/android/gradle.properties b/packages/google_sign_in/google_sign_in/example/android/gradle.properties index d12b9a8297e5..5c693e744274 100644 --- a/packages/google_sign_in/google_sign_in/example/android/gradle.properties +++ b/packages/google_sign_in/google_sign_in/example/android/gradle.properties @@ -1,3 +1,3 @@ -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx4G android.enableR8=true android.useAndroidX=true diff --git a/packages/google_sign_in/google_sign_in_android/example/android/gradle.properties b/packages/google_sign_in/google_sign_in_android/example/android/gradle.properties index d12b9a8297e5..5c693e744274 100644 --- a/packages/google_sign_in/google_sign_in_android/example/android/gradle.properties +++ b/packages/google_sign_in/google_sign_in_android/example/android/gradle.properties @@ -1,3 +1,3 @@ -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx4G android.enableR8=true android.useAndroidX=true diff --git a/packages/image_picker/image_picker/example/android/gradle.properties b/packages/image_picker/image_picker/example/android/gradle.properties index 38c8d4544ff1..2f3603c9ff62 100755 --- a/packages/image_picker/image_picker/example/android/gradle.properties +++ b/packages/image_picker/image_picker/example/android/gradle.properties @@ -1,4 +1,4 @@ -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx4G android.enableR8=true android.useAndroidX=true android.enableJetifier=true diff --git a/packages/image_picker/image_picker_android/example/android/gradle.properties b/packages/image_picker/image_picker_android/example/android/gradle.properties index 94adc3a3f97a..598d13fee446 100755 --- a/packages/image_picker/image_picker_android/example/android/gradle.properties +++ b/packages/image_picker/image_picker_android/example/android/gradle.properties @@ -1,3 +1,3 @@ -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx4G android.useAndroidX=true android.enableJetifier=true diff --git a/packages/in_app_purchase/in_app_purchase/example/android/gradle.properties b/packages/in_app_purchase/in_app_purchase/example/android/gradle.properties index 38c8d4544ff1..2f3603c9ff62 100644 --- a/packages/in_app_purchase/in_app_purchase/example/android/gradle.properties +++ b/packages/in_app_purchase/in_app_purchase/example/android/gradle.properties @@ -1,4 +1,4 @@ -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx4G android.enableR8=true android.useAndroidX=true android.enableJetifier=true diff --git a/packages/in_app_purchase/in_app_purchase_android/example/android/gradle.properties b/packages/in_app_purchase/in_app_purchase_android/example/android/gradle.properties index 38c8d4544ff1..2f3603c9ff62 100644 --- a/packages/in_app_purchase/in_app_purchase_android/example/android/gradle.properties +++ b/packages/in_app_purchase/in_app_purchase_android/example/android/gradle.properties @@ -1,4 +1,4 @@ -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx4G android.enableR8=true android.useAndroidX=true android.enableJetifier=true diff --git a/packages/local_auth/local_auth/example/android/gradle.properties b/packages/local_auth/local_auth/example/android/gradle.properties index 7fe61a74cee0..e5611e4c7fa0 100644 --- a/packages/local_auth/local_auth/example/android/gradle.properties +++ b/packages/local_auth/local_auth/example/android/gradle.properties @@ -1,4 +1,4 @@ -org.gradle.jvmargs=-Xmx1024m +org.gradle.jvmargs=-Xmx4G android.useAndroidX=true android.enableJetifier=true android.enableR8=true diff --git a/packages/local_auth/local_auth_android/example/android/gradle.properties b/packages/local_auth/local_auth_android/example/android/gradle.properties index 7fe61a74cee0..e5611e4c7fa0 100644 --- a/packages/local_auth/local_auth_android/example/android/gradle.properties +++ b/packages/local_auth/local_auth_android/example/android/gradle.properties @@ -1,4 +1,4 @@ -org.gradle.jvmargs=-Xmx1024m +org.gradle.jvmargs=-Xmx4G android.useAndroidX=true android.enableJetifier=true android.enableR8=true diff --git a/packages/path_provider/path_provider/example/android/gradle.properties b/packages/path_provider/path_provider/example/android/gradle.properties index 38c8d4544ff1..2f3603c9ff62 100644 --- a/packages/path_provider/path_provider/example/android/gradle.properties +++ b/packages/path_provider/path_provider/example/android/gradle.properties @@ -1,4 +1,4 @@ -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx4G android.enableR8=true android.useAndroidX=true android.enableJetifier=true diff --git a/packages/path_provider/path_provider_android/example/android/gradle.properties b/packages/path_provider/path_provider_android/example/android/gradle.properties index 38c8d4544ff1..2f3603c9ff62 100644 --- a/packages/path_provider/path_provider_android/example/android/gradle.properties +++ b/packages/path_provider/path_provider_android/example/android/gradle.properties @@ -1,4 +1,4 @@ -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx4G android.enableR8=true android.useAndroidX=true android.enableJetifier=true diff --git a/packages/quick_actions/quick_actions/example/android/gradle.properties b/packages/quick_actions/quick_actions/example/android/gradle.properties index 38c8d4544ff1..2f3603c9ff62 100644 --- a/packages/quick_actions/quick_actions/example/android/gradle.properties +++ b/packages/quick_actions/quick_actions/example/android/gradle.properties @@ -1,4 +1,4 @@ -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx4G android.enableR8=true android.useAndroidX=true android.enableJetifier=true diff --git a/packages/quick_actions/quick_actions_android/example/android/gradle.properties b/packages/quick_actions/quick_actions_android/example/android/gradle.properties index 38c8d4544ff1..2f3603c9ff62 100644 --- a/packages/quick_actions/quick_actions_android/example/android/gradle.properties +++ b/packages/quick_actions/quick_actions_android/example/android/gradle.properties @@ -1,4 +1,4 @@ -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx4G android.enableR8=true android.useAndroidX=true android.enableJetifier=true diff --git a/packages/shared_preferences/shared_preferences/example/android/gradle.properties b/packages/shared_preferences/shared_preferences/example/android/gradle.properties index 94adc3a3f97a..598d13fee446 100644 --- a/packages/shared_preferences/shared_preferences/example/android/gradle.properties +++ b/packages/shared_preferences/shared_preferences/example/android/gradle.properties @@ -1,3 +1,3 @@ -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx4G android.useAndroidX=true android.enableJetifier=true diff --git a/packages/shared_preferences/shared_preferences_android/example/android/gradle.properties b/packages/shared_preferences/shared_preferences_android/example/android/gradle.properties index 94adc3a3f97a..598d13fee446 100644 --- a/packages/shared_preferences/shared_preferences_android/example/android/gradle.properties +++ b/packages/shared_preferences/shared_preferences_android/example/android/gradle.properties @@ -1,3 +1,3 @@ -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx4G android.useAndroidX=true android.enableJetifier=true diff --git a/packages/url_launcher/url_launcher/example/android/gradle.properties b/packages/url_launcher/url_launcher/example/android/gradle.properties index a6738207fd15..e5611e4c7fa0 100644 --- a/packages/url_launcher/url_launcher/example/android/gradle.properties +++ b/packages/url_launcher/url_launcher/example/android/gradle.properties @@ -1,4 +1,4 @@ -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx4G android.useAndroidX=true android.enableJetifier=true android.enableR8=true diff --git a/packages/url_launcher/url_launcher_android/example/android/gradle.properties b/packages/url_launcher/url_launcher_android/example/android/gradle.properties index a6738207fd15..e5611e4c7fa0 100644 --- a/packages/url_launcher/url_launcher_android/example/android/gradle.properties +++ b/packages/url_launcher/url_launcher_android/example/android/gradle.properties @@ -1,4 +1,4 @@ -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx4G android.useAndroidX=true android.enableJetifier=true android.enableR8=true diff --git a/packages/video_player/video_player/example/android/gradle.properties b/packages/video_player/video_player/example/android/gradle.properties index a6738207fd15..e5611e4c7fa0 100644 --- a/packages/video_player/video_player/example/android/gradle.properties +++ b/packages/video_player/video_player/example/android/gradle.properties @@ -1,4 +1,4 @@ -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx4G android.useAndroidX=true android.enableJetifier=true android.enableR8=true diff --git a/packages/video_player/video_player_android/example/android/gradle.properties b/packages/video_player/video_player_android/example/android/gradle.properties index a6738207fd15..e5611e4c7fa0 100644 --- a/packages/video_player/video_player_android/example/android/gradle.properties +++ b/packages/video_player/video_player_android/example/android/gradle.properties @@ -1,4 +1,4 @@ -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx4G android.useAndroidX=true android.enableJetifier=true android.enableR8=true diff --git a/packages/webview_flutter/webview_flutter/example/android/gradle.properties b/packages/webview_flutter/webview_flutter/example/android/gradle.properties index a6738207fd15..e5611e4c7fa0 100644 --- a/packages/webview_flutter/webview_flutter/example/android/gradle.properties +++ b/packages/webview_flutter/webview_flutter/example/android/gradle.properties @@ -1,4 +1,4 @@ -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx4G android.useAndroidX=true android.enableJetifier=true android.enableR8=true diff --git a/packages/webview_flutter/webview_flutter_android/example/android/gradle.properties b/packages/webview_flutter/webview_flutter_android/example/android/gradle.properties index 94adc3a3f97a..598d13fee446 100644 --- a/packages/webview_flutter/webview_flutter_android/example/android/gradle.properties +++ b/packages/webview_flutter/webview_flutter_android/example/android/gradle.properties @@ -1,3 +1,3 @@ -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx4G android.useAndroidX=true android.enableJetifier=true From f1a3fea7f5bf78f2013959d005a7c648ef45407a Mon Sep 17 00:00:00 2001 From: Drew Roen <102626803+drewroengoogle@users.noreply.github.com> Date: Mon, 13 Feb 2023 19:24:09 -0600 Subject: [PATCH 097/130] Update GCLOUD_FIREBASE_TESTLAB_KEY (#7176) --- .cirrus.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.cirrus.yml b/.cirrus.yml index 2490255dc17c..a5292c98f209 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -248,7 +248,7 @@ task: CHANNEL: "master" CHANNEL: "stable" MAPS_API_KEY: ENCRYPTED[596a9f6bca436694625ac50851dc5da6b4d34cba8025f7db5bc9465142e8cd44e15f69e3507787753accebfc4910d550] - GCLOUD_FIREBASE_TESTLAB_KEY: ENCRYPTED[4457646586de940f49e054de7d82e60078b205ac627f11a89d077e63f639c9ba1002541d9209a9ee7777e159e97b43d0] + GCLOUD_FIREBASE_TESTLAB_KEY: ENCRYPTED[df5cf97036c09184e386edbf4ab1e741189e0ac5ca7e4c73673c4bf02d8709c9ac733597e8f5b6511b51eafb52e4027f] build_script: - ./script/tool_runner.sh build-examples --apk lint_script: From 9c312d4d2f5fe37608e11fa68295ecb59b2a200e Mon Sep 17 00:00:00 2001 From: Braden Bagby <33461698+BradenBagby@users.noreply.github.com> Date: Tue, 14 Feb 2023 07:09:28 -0700 Subject: [PATCH 098/130] [camera] flip/change camera while recording (split out PR for cam_avfoundation and cam_android) (#7109) * setDescription in Camera platform interface * example app setup to change description mid recording * AVFoundationCamera method call to setDescription * FLTCam setup to setDescription * captureSession split into video and audio so we will be able to switch cameras without breaking the audio * renamed setDescription to setDescriptionWhileRecording since it can only be used while recording * integration tests fixed * set description while recording integration test * throws error if device not recording and setDescriptionWhileRecording is called * set description while recording test * example project setup * camera preview can be changed while recording * camera switches and keeps surface pointed to mediarecorder * small change to set autofocus when switching while recording * android video record goes through VideoRenderer to apply matrix after switching camera * switch camera uses VideoRenderer * dont use video renderer until user switches camera while recording * rotate based on initial recording direction * VideoRenderer cleanup * flutter results for setDescriptionWhileRecording * error if you setDescriptionWhileRecording while device is not recording * android tests * integration tests * method channel test * main package tests * setDescriptionWhileRecording called while no video was recording test * integration tests * dependency overrides * update readme and version * removed old TODO * removed accidental dev team ID commit * renamed local variables * use captureSessionQueue * fixed local variable name * setupCaptureVideoOutput function * createConnectionWithInput * simplified configureConnection function to re-use code on switching camera * formatting * example project dependency overrides * fixed versioning * formatting * fixed some ios native tests * fixed small bug * dont emit initialized when switching camera * ios formatting * dependency overrides for camera/example * android formatting * ios test formatted * android tests formatted * android format that I missed * other android formatting * final formatting with flutter tool * formatted android again * android license in new file * update-excerpts ran * fixed changelog * removed development team * renames configureConnection to createConnection * renames unimplemented error message * renames setDescriptionWhileRecording error to match android and the other errors * fixes formatting * removes override dependencies from camera_web and camera_windows * removes camera_web override dependency in camera package * Update packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/Camera.java Co-authored-by: Camille Simon <43054281+camsim99@users.noreply.github.com> * Update packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/Camera.java Co-authored-by: Camille Simon <43054281+camsim99@users.noreply.github.com> * Update packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/Camera.java Co-authored-by: Camille Simon <43054281+camsim99@users.noreply.github.com> * Update packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/Camera.java Co-authored-by: Camille Simon <43054281+camsim99@users.noreply.github.com> * reformats camera.java * VideoRenderer uses surface texture timestamp instead of current system time * formats VideoRenderer.java * fixes comments in VideoRenderer.java * Update packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart Co-authored-by: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> * Update packages/camera/camera/lib/src/camera_controller.dart Co-authored-by: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> * renames error typo * frees shaders after program linking * handles eglSwapBuffers errors * extension check guards eglPresentationTimeANDROID * cleans openGL resources * reverted timestamp to use uptimeMillis() * Tests for startPreviewWithVideoRendererStream * fixes exception not being caught * tests for correct rotation to be set * fixes versioning * tests method channel setDescriptionWhileRecording * adds forwarding getter on CameraController to its value's description * dummy commit to fix github test's not finding commit hash * adds override description for FakeController in camera tests * fixes versioning for avfoundation and android * fixes versioning * fixes pubspec versions * ios setDescription * setDescription * android setDescription * formatting * revert * nits and reverts * nits * fixes README * fixes other comments * fixes setDescription override in camera_preview_test * set description test * versions * removes changes on platform_interface_changes * points all packages to platform interface version 2.4 * points to the new platform interface * removes everything that isnt under camera_avfoundation and camera_android * removes dependency overrides in examples * removes version change on camera * removes camera changes that were missed * fixes android version --------- Co-authored-by: BradenBagby Co-authored-by: Camille Simon <43054281+camsim99@users.noreply.github.com> Co-authored-by: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> --- packages/camera/camera_android/CHANGELOG.md | 4 + .../io/flutter/plugins/camera/Camera.java | 167 ++++++-- .../plugins/camera/MethodCallHandlerImpl.java | 12 + .../flutter/plugins/camera/VideoRenderer.java | 365 ++++++++++++++++++ .../io/flutter/plugins/camera/CameraTest.java | 109 ++++++ .../example/integration_test/camera_test.dart | 45 +++ .../example/lib/camera_controller.dart | 30 +- .../camera_android/example/lib/main.dart | 23 +- .../camera_android/example/pubspec.yaml | 3 +- .../lib/src/android_camera.dart | 11 + packages/camera/camera_android/pubspec.yaml | 4 +- .../test/android_camera_test.dart | 23 ++ .../camera/camera_avfoundation/CHANGELOG.md | 4 + .../example/integration_test/camera_test.dart | 45 +++ .../ios/Runner.xcodeproj/project.pbxproj | 2 +- .../example/ios/Runner/Info.plist | 2 + .../example/ios/RunnerTests/CameraTestUtils.m | 13 +- .../example/lib/camera_controller.dart | 30 +- .../camera_avfoundation/example/lib/main.dart | 23 +- .../camera_avfoundation/example/pubspec.yaml | 2 +- .../ios/Classes/CameraPlugin.m | 2 + .../camera_avfoundation/ios/Classes/FLTCam.h | 2 + .../camera_avfoundation/ios/Classes/FLTCam.m | 177 ++++++--- .../ios/Classes/FLTCam_Test.h | 3 +- .../lib/src/avfoundation_camera.dart | 11 + .../camera/camera_avfoundation/pubspec.yaml | 4 +- .../test/avfoundation_camera_test.dart | 23 ++ 27 files changed, 1014 insertions(+), 125 deletions(-) create mode 100644 packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/VideoRenderer.java diff --git a/packages/camera/camera_android/CHANGELOG.md b/packages/camera/camera_android/CHANGELOG.md index 4609b402058a..f7f0b2a0343a 100644 --- a/packages/camera/camera_android/CHANGELOG.md +++ b/packages/camera/camera_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.10.5 + +* Allows camera to be switched while video recording. + ## 0.10.4 * Temporarily fixes issue with requested video profiles being null by falling back to deprecated behavior in that case. diff --git a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/Camera.java b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/Camera.java index b02d6864b5b7..c2255e23273a 100644 --- a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/Camera.java +++ b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/Camera.java @@ -115,13 +115,28 @@ class Camera * Holds all of the camera features/settings and will be used to update the request builder when * one changes. */ - private final CameraFeatures cameraFeatures; + private CameraFeatures cameraFeatures; + + private String imageFormatGroup; + + /** + * Takes an input/output surface and orients the recording correctly. This is needed because + * switching cameras while recording causes the wrong orientation. + */ + private VideoRenderer videoRenderer; + + /** + * Whether or not the camera aligns with the initial way the camera was facing if the camera was + * flipped. + */ + private int initialCameraFacing; private final SurfaceTextureEntry flutterTexture; + private final ResolutionPreset resolutionPreset; private final boolean enableAudio; private final Context applicationContext; private final DartMessenger dartMessenger; - private final CameraProperties cameraProperties; + private CameraProperties cameraProperties; private final CameraFeatureFactory cameraFeatureFactory; private final Activity activity; /** A {@link CameraCaptureSession.CaptureCallback} that handles events related to JPEG capture. */ @@ -211,6 +226,7 @@ public Camera( this.applicationContext = activity.getApplicationContext(); this.cameraProperties = cameraProperties; this.cameraFeatureFactory = cameraFeatureFactory; + this.resolutionPreset = resolutionPreset; this.cameraFeatures = CameraFeatures.init( cameraFeatureFactory, cameraProperties, activity, dartMessenger, resolutionPreset); @@ -251,6 +267,7 @@ private void prepareMediaRecorder(String outputFilePath) throws IOException { if (mediaRecorder != null) { mediaRecorder.release(); } + closeRenderer(); final PlatformChannel.DeviceOrientation lockedOrientation = ((SensorOrientationFeature) cameraFeatures.getSensorOrientation()) @@ -279,6 +296,7 @@ private void prepareMediaRecorder(String outputFilePath) throws IOException { @SuppressLint("MissingPermission") public void open(String imageFormatGroup) throws CameraAccessException { + this.imageFormatGroup = imageFormatGroup; final ResolutionFeature resolutionFeature = cameraFeatures.getResolution(); if (!resolutionFeature.checkIsSupported()) { @@ -323,14 +341,16 @@ public void onOpened(@NonNull CameraDevice device) { cameraDevice = new DefaultCameraDeviceWrapper(device); try { startPreview(); + if (!recordingVideo) // only send initialization if we werent already recording and switching cameras dartMessenger.sendCameraInitializedEvent( - resolutionFeature.getPreviewSize().getWidth(), - resolutionFeature.getPreviewSize().getHeight(), - cameraFeatures.getExposureLock().getValue(), - cameraFeatures.getAutoFocus().getValue(), - cameraFeatures.getExposurePoint().checkIsSupported(), - cameraFeatures.getFocusPoint().checkIsSupported()); - } catch (CameraAccessException e) { + resolutionFeature.getPreviewSize().getWidth(), + resolutionFeature.getPreviewSize().getHeight(), + cameraFeatures.getExposureLock().getValue(), + cameraFeatures.getAutoFocus().getValue(), + cameraFeatures.getExposurePoint().checkIsSupported(), + cameraFeatures.getFocusPoint().checkIsSupported()); + + } catch (CameraAccessException | InterruptedException e) { dartMessenger.sendCameraErrorEvent(e.getMessage()); close(); } @@ -340,7 +360,8 @@ public void onOpened(@NonNull CameraDevice device) { public void onClosed(@NonNull CameraDevice camera) { Log.i(TAG, "open | onClosed"); - // Prevents calls to methods that would otherwise result in IllegalStateException exceptions. + // Prevents calls to methods that would otherwise result in IllegalStateException + // exceptions. cameraDevice = null; closeCaptureSession(); dartMessenger.sendCameraClosingEvent(); @@ -756,7 +777,7 @@ public void startVideoRecording( if (imageStreamChannel != null) { setStreamHandler(imageStreamChannel); } - + initialCameraFacing = cameraProperties.getLensFacing(); recordingVideo = true; try { startCapture(true, imageStreamChannel != null); @@ -768,6 +789,13 @@ public void startVideoRecording( } } + private void closeRenderer() { + if (videoRenderer != null) { + videoRenderer.close(); + videoRenderer = null; + } + } + public void stopVideoRecording(@NonNull final Result result) { if (!recordingVideo) { result.success(null); @@ -778,6 +806,7 @@ public void stopVideoRecording(@NonNull final Result result) { cameraFeatureFactory.createAutoFocusFeature(cameraProperties, false)); recordingVideo = false; try { + closeRenderer(); captureSession.abortCaptures(); mediaRecorder.stop(); } catch (CameraAccessException | IllegalStateException e) { @@ -786,7 +815,7 @@ public void stopVideoRecording(@NonNull final Result result) { mediaRecorder.reset(); try { startPreview(); - } catch (CameraAccessException | IllegalStateException e) { + } catch (CameraAccessException | IllegalStateException | InterruptedException e) { result.error("videoRecordingFailed", e.getMessage(), null); return; } @@ -1070,13 +1099,51 @@ public void resumePreview() { null, (code, message) -> dartMessenger.sendCameraErrorEvent(message)); } - public void startPreview() throws CameraAccessException { + public void startPreview() throws CameraAccessException, InterruptedException { + // If recording is already in progress, the camera is being flipped, so send it through the VideoRenderer to keep the correct orientation. + if (recordingVideo) { + startPreviewWithVideoRendererStream(); + } else { + startRegularPreview(); + } + } + + private void startRegularPreview() throws CameraAccessException { if (pictureImageReader == null || pictureImageReader.getSurface() == null) return; Log.i(TAG, "startPreview"); - createCaptureSession(CameraDevice.TEMPLATE_PREVIEW, pictureImageReader.getSurface()); } + private void startPreviewWithVideoRendererStream() + throws CameraAccessException, InterruptedException { + if (videoRenderer == null) return; + + // get rotation for rendered video + final PlatformChannel.DeviceOrientation lockedOrientation = + ((SensorOrientationFeature) cameraFeatures.getSensorOrientation()) + .getLockedCaptureOrientation(); + DeviceOrientationManager orientationManager = + cameraFeatures.getSensorOrientation().getDeviceOrientationManager(); + + int rotation = 0; + if (orientationManager != null) { + rotation = + lockedOrientation == null + ? orientationManager.getVideoOrientation() + : orientationManager.getVideoOrientation(lockedOrientation); + } + + if (cameraProperties.getLensFacing() != initialCameraFacing) { + + // If the new camera is facing the opposite way than the initial recording, + // the rotation should be flipped 180 degrees. + rotation = (rotation + 180) % 360; + } + videoRenderer.setRotation(rotation); + + createCaptureSession(CameraDevice.TEMPLATE_RECORD, videoRenderer.getInputSurface()); + } + public void startPreviewWithImageStream(EventChannel imageStreamChannel) throws CameraAccessException { setStreamHandler(imageStreamChannel); @@ -1200,17 +1267,7 @@ private void closeCaptureSession() { public void close() { Log.i(TAG, "close"); - if (cameraDevice != null) { - cameraDevice.close(); - cameraDevice = null; - - // Closing the CameraDevice without closing the CameraCaptureSession is recommended - // for quickly closing the camera: - // https://developer.android.com/reference/android/hardware/camera2/CameraCaptureSession#close() - captureSession = null; - } else { - closeCaptureSession(); - } + stopAndReleaseCamera(); if (pictureImageReader != null) { pictureImageReader.close(); @@ -1229,6 +1286,66 @@ public void close() { stopBackgroundThread(); } + private void stopAndReleaseCamera() { + if (cameraDevice != null) { + cameraDevice.close(); + cameraDevice = null; + + // Closing the CameraDevice without closing the CameraCaptureSession is recommended + // for quickly closing the camera: + // https://developer.android.com/reference/android/hardware/camera2/CameraCaptureSession#close() + captureSession = null; + } else { + closeCaptureSession(); + } + } + + private void prepareVideoRenderer() { + if (videoRenderer != null) return; + final ResolutionFeature resolutionFeature = cameraFeatures.getResolution(); + + // handle videoRenderer errors + Thread.UncaughtExceptionHandler videoRendererUncaughtExceptionHandler = + new Thread.UncaughtExceptionHandler() { + @Override + public void uncaughtException(Thread thread, Throwable ex) { + dartMessenger.sendCameraErrorEvent( + "Failed to process frames after camera was flipped."); + } + }; + + videoRenderer = + new VideoRenderer( + mediaRecorder.getSurface(), + resolutionFeature.getCaptureSize().getWidth(), + resolutionFeature.getCaptureSize().getHeight(), + videoRendererUncaughtExceptionHandler); + } + + public void setDescriptionWhileRecording( + @NonNull final Result result, CameraProperties properties) { + + if (!recordingVideo) { + result.error("setDescriptionWhileRecordingFailed", "Device was not recording", null); + return; + } + + stopAndReleaseCamera(); + prepareVideoRenderer(); + cameraProperties = properties; + cameraFeatures = + CameraFeatures.init( + cameraFeatureFactory, cameraProperties, activity, dartMessenger, resolutionPreset); + cameraFeatures.setAutoFocus( + cameraFeatureFactory.createAutoFocusFeature(cameraProperties, true)); + try { + open(imageFormatGroup); + } catch (CameraAccessException e) { + result.error("setDescriptionWhileRecordingFailed", e.getMessage(), null); + } + result.success(null); + } + public void dispose() { Log.i(TAG, "dispose"); diff --git a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java index 432344ade8cd..aad62bbaba85 100644 --- a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java +++ b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java @@ -354,6 +354,18 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result) result.success(null); break; } + case "setDescriptionWhileRecording": + { + try { + String cameraName = call.argument("cameraName"); + CameraProperties cameraProperties = + new CameraPropertiesImpl(cameraName, CameraUtils.getCameraManager(activity)); + camera.setDescriptionWhileRecording(result, cameraProperties); + } catch (Exception e) { + handleException(e, result); + } + break; + } case "dispose": { if (camera != null) { diff --git a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/VideoRenderer.java b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/VideoRenderer.java new file mode 100644 index 000000000000..b7128373b101 --- /dev/null +++ b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/VideoRenderer.java @@ -0,0 +1,365 @@ +// Copyright 2013 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.camera; + +import static android.os.SystemClock.uptimeMillis; + +import android.graphics.SurfaceTexture; +import android.opengl.EGL14; +import android.opengl.EGLConfig; +import android.opengl.EGLContext; +import android.opengl.EGLDisplay; +import android.opengl.EGLExt; +import android.opengl.EGLSurface; +import android.opengl.GLES11Ext; +import android.opengl.GLES20; +import android.opengl.GLUtils; +import android.opengl.Matrix; +import android.os.Handler; +import android.os.HandlerThread; +import android.util.Log; +import android.view.Surface; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * Renders video onto texture after performing a matrix rotation on each frame. + * + *

VideoRenderer is needed because when switching between cameras mid recording, the orientation + * of the recording from the new camera usually becomes flipped. MediaRecorder has + * setOrientationHint, but that cannot be called mid recording and therefore isn't useful. Android + * Camera2 has no setDisplayOrientation on the camera itself as it is supposed to 'just work' (see + * https://stackoverflow.com/questions/33479004/what-is-the-camera2-api-equivalent-of-setdisplayorientation). + * Therefore it cannot be used to set the camera's orientation either. + * + *

This leaves the solution to be routing the recording through a surface texture and performing + * a matrix transformation on it manually to get the correct orientation. This only happens when + * setDescription is called mid video recording. + */ +public class VideoRenderer { + + private static String TAG = "VideoRenderer"; + + private static final String vertexShaderCode = + " precision highp float;\n" + + " attribute vec3 vertexPosition;\n" + + " attribute vec2 uvs;\n" + + " varying vec2 varUvs;\n" + + " uniform mat4 texMatrix;\n" + + " uniform mat4 mvp;\n" + + "\n" + + " void main()\n" + + " {\n" + + " varUvs = (texMatrix * vec4(uvs.x, uvs.y, 0, 1.0)).xy;\n" + + " gl_Position = mvp * vec4(vertexPosition, 1.0);\n" + + " }"; + + private static final String fragmentShaderCode = + " #extension GL_OES_EGL_image_external : require\n" + + " precision mediump float;\n" + + "\n" + + " varying vec2 varUvs;\n" + + " uniform samplerExternalOES texSampler;\n" + + "\n" + + " void main()\n" + + " {\n" + + " vec4 c = texture2D(texSampler, varUvs);\n" + + " gl_FragColor = vec4(c.r, c.g, c.b, c.a);\n" + + " }"; + + private final int[] textureHandles = new int[1]; + + private final float[] vertices = + new float[] { + -1.0f, -1.0f, 0.0f, 0f, 0f, -1.0f, 1.0f, 0.0f, 0f, 1f, 1.0f, 1.0f, 0.0f, 1f, 1f, 1.0f, + -1.0f, 0.0f, 1f, 0f + }; + + private final int[] indices = new int[] {2, 1, 0, 0, 3, 2}; + + private int program; + private int vertexHandle = 0; + private final int[] bufferHandles = new int[2]; + private int uvsHandle = 0; + private int texMatrixHandle = 0; + private int mvpHandle = 0; + + EGLDisplay display; + EGLContext context; + EGLSurface surface; + private Thread thread; + private final Surface outputSurface; + private SurfaceTexture inputSurfaceTexture; + private Surface inputSurface; + + private HandlerThread surfaceTextureFrameAvailableHandler; + private final Object surfaceTextureAvailableFrameLock = new Object(); + private Boolean surfaceTextureFrameAvailable = false; + + private final int recordingWidth; + private final int recordingHeight; + private int rotation = 0; + + private final Object lock = new Object(); + + private final Thread.UncaughtExceptionHandler uncaughtExceptionHandler; + + /** Gets surface for input. Blocks until surface is ready. */ + public Surface getInputSurface() throws InterruptedException { + synchronized (lock) { + while (inputSurface == null) { + lock.wait(); + } + } + return inputSurface; + } + + public VideoRenderer( + Surface outputSurface, + int recordingWidth, + int recordingHeight, + Thread.UncaughtExceptionHandler uncaughtExceptionHandler) { + this.outputSurface = outputSurface; + this.recordingHeight = recordingHeight; + this.recordingWidth = recordingWidth; + this.uncaughtExceptionHandler = uncaughtExceptionHandler; + startOpenGL(); + Log.d(TAG, "VideoRenderer setup complete"); + } + + /** Stop rendering and cleanup resources. */ + public void close() { + thread.interrupt(); + surfaceTextureFrameAvailableHandler.quitSafely(); + cleanupOpenGL(); + inputSurfaceTexture.release(); + } + + private void cleanupOpenGL() { + GLES20.glDeleteBuffers(2, bufferHandles, 0); + GLES20.glDeleteTextures(1, textureHandles, 0); + EGL14.eglDestroyContext(display, context); + EGL14.eglDestroySurface(display, surface); + GLES20.glDeleteProgram(program); + } + + /** Configures openGL. Must be called in same thread as draw is called. */ + private void configureOpenGL() { + synchronized (lock) { + display = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY); + if (display == EGL14.EGL_NO_DISPLAY) + throw new RuntimeException( + "eglDisplay == EGL14.EGL_NO_DISPLAY: " + + GLUtils.getEGLErrorString(EGL14.eglGetError())); + + int[] version = new int[2]; + if (!EGL14.eglInitialize(display, version, 0, version, 1)) + throw new RuntimeException( + "eglInitialize(): " + GLUtils.getEGLErrorString(EGL14.eglGetError())); + + String eglExtensions = EGL14.eglQueryString(display, EGL14.EGL_EXTENSIONS); + if (!eglExtensions.contains("EGL_ANDROID_presentation_time")) + throw new RuntimeException( + "cannot configure OpenGL. missing EGL_ANDROID_presentation_time"); + + int[] attribList = + new int[] { + EGL14.EGL_RED_SIZE, 8, + EGL14.EGL_GREEN_SIZE, 8, + EGL14.EGL_BLUE_SIZE, 8, + EGL14.EGL_ALPHA_SIZE, 8, + EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT, + EGLExt.EGL_RECORDABLE_ANDROID, 1, + EGL14.EGL_NONE + }; + + EGLConfig[] configs = new EGLConfig[1]; + int[] numConfigs = new int[1]; + if (!EGL14.eglChooseConfig(display, attribList, 0, configs, 0, configs.length, numConfigs, 0)) + throw new RuntimeException(GLUtils.getEGLErrorString(EGL14.eglGetError())); + + int err = EGL14.eglGetError(); + if (err != EGL14.EGL_SUCCESS) throw new RuntimeException(GLUtils.getEGLErrorString(err)); + + int[] ctxAttribs = new int[] {EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL14.EGL_NONE}; + context = EGL14.eglCreateContext(display, configs[0], EGL14.EGL_NO_CONTEXT, ctxAttribs, 0); + + err = EGL14.eglGetError(); + if (err != EGL14.EGL_SUCCESS) throw new RuntimeException(GLUtils.getEGLErrorString(err)); + + int[] surfaceAttribs = new int[] {EGL14.EGL_NONE}; + + surface = EGL14.eglCreateWindowSurface(display, configs[0], outputSurface, surfaceAttribs, 0); + + err = EGL14.eglGetError(); + if (err != EGL14.EGL_SUCCESS) throw new RuntimeException(GLUtils.getEGLErrorString(err)); + + if (!EGL14.eglMakeCurrent(display, surface, surface, context)) + throw new RuntimeException( + "eglMakeCurrent(): " + GLUtils.getEGLErrorString(EGL14.eglGetError())); + + ByteBuffer vertexBuffer = ByteBuffer.allocateDirect(vertices.length * 4); + vertexBuffer.order(ByteOrder.nativeOrder()); + vertexBuffer.asFloatBuffer().put(vertices); + vertexBuffer.asFloatBuffer().position(0); + + ByteBuffer indexBuffer = ByteBuffer.allocateDirect(indices.length * 4); + indexBuffer.order(ByteOrder.nativeOrder()); + indexBuffer.asIntBuffer().put(indices); + indexBuffer.position(0); + + int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode); + int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode); + + program = GLES20.glCreateProgram(); + + GLES20.glAttachShader(program, vertexShader); + GLES20.glAttachShader(program, fragmentShader); + GLES20.glLinkProgram(program); + + deleteShader(vertexShader); + deleteShader(fragmentShader); + + vertexHandle = GLES20.glGetAttribLocation(program, "vertexPosition"); + uvsHandle = GLES20.glGetAttribLocation(program, "uvs"); + texMatrixHandle = GLES20.glGetUniformLocation(program, "texMatrix"); + mvpHandle = GLES20.glGetUniformLocation(program, "mvp"); + + // Initialize buffers + GLES20.glGenBuffers(2, bufferHandles, 0); + + GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, bufferHandles[0]); + GLES20.glBufferData( + GLES20.GL_ARRAY_BUFFER, vertices.length * 4, vertexBuffer, GLES20.GL_DYNAMIC_DRAW); + + GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, bufferHandles[1]); + GLES20.glBufferData( + GLES20.GL_ELEMENT_ARRAY_BUFFER, indices.length * 4, indexBuffer, GLES20.GL_DYNAMIC_DRAW); + + // Init texture that will receive decoded frames + GLES20.glGenTextures(1, textureHandles, 0); + GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureHandles[0]); + + inputSurfaceTexture = new SurfaceTexture(getTexId()); + inputSurfaceTexture.setDefaultBufferSize(recordingWidth, recordingHeight); + surfaceTextureFrameAvailableHandler = new HandlerThread("FrameHandlerThread"); + surfaceTextureFrameAvailableHandler.start(); + inputSurface = new Surface(inputSurfaceTexture); + + inputSurfaceTexture.setOnFrameAvailableListener( + new SurfaceTexture.OnFrameAvailableListener() { + @Override + public void onFrameAvailable(SurfaceTexture surfaceTexture) { + synchronized (surfaceTextureAvailableFrameLock) { + if (surfaceTextureFrameAvailable) + Log.w(TAG, "Frame available before processing other frames. dropping frames"); + surfaceTextureFrameAvailable = true; + surfaceTextureAvailableFrameLock.notifyAll(); + } + } + }, + new Handler(surfaceTextureFrameAvailableHandler.getLooper())); + lock.notifyAll(); + } + } + + /** Starts and configures Video Renderer. */ + private void startOpenGL() { + Log.d(TAG, "Starting OpenGL Thread"); + thread = + new Thread() { + @Override + public void run() { + + configureOpenGL(); + + try { + // Continuously pull frames from input surface texture and use videoRenderer to modify + // to correct rotation. + while (!Thread.interrupted()) { + + synchronized (surfaceTextureAvailableFrameLock) { + while (!surfaceTextureFrameAvailable) { + surfaceTextureAvailableFrameLock.wait(500); + } + surfaceTextureFrameAvailable = false; + } + + inputSurfaceTexture.updateTexImage(); + + float[] surfaceTextureMatrix = new float[16]; + inputSurfaceTexture.getTransformMatrix(surfaceTextureMatrix); + + draw(recordingWidth, recordingHeight, surfaceTextureMatrix); + } + } catch (InterruptedException e) { + Log.d(TAG, "thread interrupted while waiting for frames"); + } + } + }; + thread.setUncaughtExceptionHandler(uncaughtExceptionHandler); + thread.start(); + } + + public int getTexId() { + return textureHandles[0]; + } + + public float[] moveMatrix() { + float[] m = new float[16]; + Matrix.setIdentityM(m, 0); + Matrix.rotateM(m, 0, rotation, 0, 0, 1); + return m; + } + + public void setRotation(int rotation) { + this.rotation = rotation; + } + + private int loadShader(int type, String code) { + + int shader = GLES20.glCreateShader(type); + + GLES20.glShaderSource(shader, code); + GLES20.glCompileShader(shader); + return shader; + } + + private void deleteShader(int shader) { + GLES20.glDeleteShader(shader); + } + + public void draw(int viewportWidth, int viewportHeight, float[] texMatrix) { + + GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT); + GLES20.glClearColor(0f, 0f, 0f, 0f); + + GLES20.glViewport(0, 0, viewportWidth, viewportHeight); + + GLES20.glUseProgram(program); + + // Pass transformations to shader + GLES20.glUniformMatrix4fv(texMatrixHandle, 1, false, texMatrix, 0); + GLES20.glUniformMatrix4fv(mvpHandle, 1, false, moveMatrix(), 0); + + // Prepare buffers with vertices and indices & draw + GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, bufferHandles[0]); + GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, bufferHandles[1]); + + GLES20.glEnableVertexAttribArray(vertexHandle); + GLES20.glVertexAttribPointer(vertexHandle, 3, GLES20.GL_FLOAT, false, 4 * 5, 0); + + GLES20.glEnableVertexAttribArray(uvsHandle); + GLES20.glVertexAttribPointer(uvsHandle, 2, GLES20.GL_FLOAT, false, 4 * 5, 3 * 4); + + GLES20.glDrawElements(GLES20.GL_TRIANGLES, 6, GLES20.GL_UNSIGNED_INT, 0); + + EGLExt.eglPresentationTimeANDROID(display, surface, uptimeMillis() * 1000000); + if (!EGL14.eglSwapBuffers(display, surface)) { + throw new RuntimeException( + "eglSwapBuffers()" + GLUtils.getEGLErrorString(EGL14.eglGetError())); + } + } +} diff --git a/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraTest.java b/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraTest.java index 9a679017ded2..9a6f7dc20d22 100644 --- a/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraTest.java +++ b/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraTest.java @@ -602,6 +602,115 @@ public void resumeVideoRecording_shouldCallPauseWhenRecordingAndOnAPIN() { verify(mockResult, never()).error(any(), any(), any()); } + @Test + public void setDescriptionWhileRecording() { + MethodChannel.Result mockResult = mock(MethodChannel.Result.class); + MediaRecorder mockMediaRecorder = mock(MediaRecorder.class); + VideoRenderer mockVideoRenderer = mock(VideoRenderer.class); + TestUtils.setPrivateField(camera, "mediaRecorder", mockMediaRecorder); + TestUtils.setPrivateField(camera, "recordingVideo", true); + TestUtils.setPrivateField(camera, "videoRenderer", mockVideoRenderer); + + final CameraProperties newCameraProperties = mock(CameraProperties.class); + camera.setDescriptionWhileRecording(mockResult, newCameraProperties); + + verify(mockResult, times(1)).success(null); + verify(mockResult, never()).error(any(), any(), any()); + } + + @Test + public void startPreview_shouldPullStreamFromVideoRenderer() + throws InterruptedException, CameraAccessException { + VideoRenderer mockVideoRenderer = mock(VideoRenderer.class); + ArrayList mockRequestBuilders = new ArrayList<>(); + mockRequestBuilders.add(mock(CaptureRequest.Builder.class)); + SurfaceTexture mockSurfaceTexture = mock(SurfaceTexture.class); + Size mockSize = mock(Size.class); + TestUtils.setPrivateField(camera, "recordingVideo", true); + TestUtils.setPrivateField(camera, "videoRenderer", mockVideoRenderer); + CameraDeviceWrapper fakeCamera = new FakeCameraDeviceWrapper(mockRequestBuilders); + TestUtils.setPrivateField(camera, "cameraDevice", fakeCamera); + + TextureRegistry.SurfaceTextureEntry cameraFlutterTexture = + (TextureRegistry.SurfaceTextureEntry) TestUtils.getPrivateField(camera, "flutterTexture"); + ResolutionFeature resolutionFeature = + (ResolutionFeature) + TestUtils.getPrivateField(mockCameraFeatureFactory, "mockResolutionFeature"); + + when(cameraFlutterTexture.surfaceTexture()).thenReturn(mockSurfaceTexture); + when(resolutionFeature.getPreviewSize()).thenReturn(mockSize); + + camera.startPreview(); + verify(mockVideoRenderer, times(1)) + .getInputSurface(); // stream pulled from videoRenderer's surface. + } + + @Test + public void startPreview_shouldPullStreamFromImageReader() + throws InterruptedException, CameraAccessException { + ArrayList mockRequestBuilders = new ArrayList<>(); + mockRequestBuilders.add(mock(CaptureRequest.Builder.class)); + SurfaceTexture mockSurfaceTexture = mock(SurfaceTexture.class); + Size mockSize = mock(Size.class); + ImageReader mockImageReader = mock(ImageReader.class); + TestUtils.setPrivateField(camera, "recordingVideo", false); + TestUtils.setPrivateField(camera, "pictureImageReader", mockImageReader); + CameraDeviceWrapper fakeCamera = new FakeCameraDeviceWrapper(mockRequestBuilders); + TestUtils.setPrivateField(camera, "cameraDevice", fakeCamera); + + TextureRegistry.SurfaceTextureEntry cameraFlutterTexture = + (TextureRegistry.SurfaceTextureEntry) TestUtils.getPrivateField(camera, "flutterTexture"); + ResolutionFeature resolutionFeature = + (ResolutionFeature) + TestUtils.getPrivateField(mockCameraFeatureFactory, "mockResolutionFeature"); + + when(cameraFlutterTexture.surfaceTexture()).thenReturn(mockSurfaceTexture); + when(resolutionFeature.getPreviewSize()).thenReturn(mockSize); + + camera.startPreview(); + verify(mockImageReader, times(1)) + .getSurface(); // stream pulled from regular imageReader's surface. + } + + @Test + public void startPreview_shouldFlipRotation() throws InterruptedException, CameraAccessException { + VideoRenderer mockVideoRenderer = mock(VideoRenderer.class); + ArrayList mockRequestBuilders = new ArrayList<>(); + mockRequestBuilders.add(mock(CaptureRequest.Builder.class)); + SurfaceTexture mockSurfaceTexture = mock(SurfaceTexture.class); + Size mockSize = mock(Size.class); + TestUtils.setPrivateField(camera, "recordingVideo", true); + TestUtils.setPrivateField(camera, "videoRenderer", mockVideoRenderer); + TestUtils.setPrivateField(camera, "initialCameraFacing", CameraMetadata.LENS_FACING_BACK); + CameraDeviceWrapper fakeCamera = new FakeCameraDeviceWrapper(mockRequestBuilders); + TestUtils.setPrivateField(camera, "cameraDevice", fakeCamera); + + TextureRegistry.SurfaceTextureEntry cameraFlutterTexture = + (TextureRegistry.SurfaceTextureEntry) TestUtils.getPrivateField(camera, "flutterTexture"); + ResolutionFeature resolutionFeature = + (ResolutionFeature) + TestUtils.getPrivateField(mockCameraFeatureFactory, "mockResolutionFeature"); + + when(cameraFlutterTexture.surfaceTexture()).thenReturn(mockSurfaceTexture); + when(resolutionFeature.getPreviewSize()).thenReturn(mockSize); + when(mockCameraProperties.getLensFacing()).thenReturn(CameraMetadata.LENS_FACING_FRONT); + + camera.startPreview(); + verify(mockVideoRenderer, times(1)).setRotation(180); + } + + @Test + public void setDescriptionWhileRecording_shouldErrorWhenNotRecording() { + MethodChannel.Result mockResult = mock(MethodChannel.Result.class); + TestUtils.setPrivateField(camera, "recordingVideo", false); + final CameraProperties newCameraProperties = mock(CameraProperties.class); + camera.setDescriptionWhileRecording(mockResult, newCameraProperties); + + verify(mockResult, times(1)) + .error("setDescriptionWhileRecordingFailed", "Device was not recording", null); + verify(mockResult, never()).success(any()); + } + @Test public void resumeVideoRecording_shouldSendVideoRecordingFailedErrorWhenVersionCodeSmallerThanN() { diff --git a/packages/camera/camera_android/example/integration_test/camera_test.dart b/packages/camera/camera_android/example/integration_test/camera_test.dart index e499872da5f3..517a50d02cc5 100644 --- a/packages/camera/camera_android/example/integration_test/camera_test.dart +++ b/packages/camera/camera_android/example/integration_test/camera_test.dart @@ -205,6 +205,51 @@ void main() { expect(duration, lessThan(recordingTime - timePaused)); }); + testWidgets('Set description while recording', (WidgetTester tester) async { + final List cameras = + await CameraPlatform.instance.availableCameras(); + if (cameras.length < 2) { + return; + } + + final CameraController controller = CameraController( + cameras[0], + ResolutionPreset.low, + enableAudio: false, + ); + + await controller.initialize(); + await controller.prepareForVideoRecording(); + + await controller.startVideoRecording(); + sleep(const Duration(milliseconds: 500)); + await controller.setDescription(cameras[1]); + sleep(const Duration(milliseconds: 500)); + + expect(controller.description, cameras[1]); + }); + + testWidgets('Set description', (WidgetTester tester) async { + final List cameras = + await CameraPlatform.instance.availableCameras(); + if (cameras.length < 2) { + return; + } + + final CameraController controller = CameraController( + cameras[0], + ResolutionPreset.low, + enableAudio: false, + ); + + await controller.initialize(); + sleep(const Duration(milliseconds: 500)); + await controller.setDescription(cameras[1]); + sleep(const Duration(milliseconds: 500)); + + expect(controller.description, cameras[1]); + }); + testWidgets( 'image streaming', (WidgetTester tester) async { diff --git a/packages/camera/camera_android/example/lib/camera_controller.dart b/packages/camera/camera_android/example/lib/camera_controller.dart index 8139dcdb0220..fd4f09a027b9 100644 --- a/packages/camera/camera_android/example/lib/camera_controller.dart +++ b/packages/camera/camera_android/example/lib/camera_controller.dart @@ -24,6 +24,7 @@ class CameraValue { required this.exposureMode, required this.focusMode, required this.deviceOrientation, + required this.description, this.lockedCaptureOrientation, this.recordingOrientation, this.isPreviewPaused = false, @@ -31,7 +32,7 @@ class CameraValue { }); /// Creates a new camera controller state for an uninitialized controller. - const CameraValue.uninitialized() + const CameraValue.uninitialized(CameraDescription description) : this( isInitialized: false, isRecordingVideo: false, @@ -43,6 +44,7 @@ class CameraValue { focusMode: FocusMode.auto, deviceOrientation: DeviceOrientation.portraitUp, isPreviewPaused: false, + description: description, ); /// True after [CameraController.initialize] has completed successfully. @@ -92,6 +94,9 @@ class CameraValue { /// The orientation of the currently running video recording. final DeviceOrientation? recordingOrientation; + /// The properties of the camera device controlled by this controller. + final CameraDescription description; + /// Creates a modified copy of the object. /// /// Explicitly specified fields get the specified value, all other fields get @@ -112,6 +117,7 @@ class CameraValue { Optional? lockedCaptureOrientation, Optional? recordingOrientation, bool? isPreviewPaused, + CameraDescription? description, Optional? previewPauseOrientation, }) { return CameraValue( @@ -132,6 +138,7 @@ class CameraValue { ? this.recordingOrientation : recordingOrientation.orNull, isPreviewPaused: isPreviewPaused ?? this.isPreviewPaused, + description: description ?? this.description, previewPauseOrientation: previewPauseOrientation == null ? this.previewPauseOrientation : previewPauseOrientation.orNull, @@ -165,14 +172,14 @@ class CameraValue { class CameraController extends ValueNotifier { /// Creates a new camera controller in an uninitialized state. CameraController( - this.description, + CameraDescription cameraDescription, this.resolutionPreset, { this.enableAudio = true, this.imageFormatGroup, - }) : super(const CameraValue.uninitialized()); + }) : super(CameraValue.uninitialized(cameraDescription)); /// The properties of the camera device controlled by this controller. - final CameraDescription description; + CameraDescription get description => value.description; /// The resolution this controller is targeting. /// @@ -202,7 +209,9 @@ class CameraController extends ValueNotifier { int get cameraId => _cameraId; /// Initializes the camera on the device. - Future initialize() async { + Future initialize() => _initializeWithDescription(description); + + Future _initializeWithDescription(CameraDescription description) async { final Completer initializeCompleter = Completer(); @@ -234,6 +243,7 @@ class CameraController extends ValueNotifier { value = value.copyWith( isInitialized: true, + description: description, previewSize: await initializeCompleter.future .then((CameraInitializedEvent event) => Size( event.previewWidth, @@ -274,6 +284,16 @@ class CameraController extends ValueNotifier { previewPauseOrientation: const Optional.absent()); } + /// Sets the description of the camera. + Future setDescription(CameraDescription description) async { + if (value.isRecordingVideo) { + await CameraPlatform.instance.setDescriptionWhileRecording(description); + value = value.copyWith(description: description); + } else { + await _initializeWithDescription(description); + } + } + /// Captures an image and returns the file where it was saved. /// /// Throws a [CameraException] if the capture fails. diff --git a/packages/camera/camera_android/example/lib/main.dart b/packages/camera/camera_android/example/lib/main.dart index 4d98aed9a4c2..3731325a49fd 100644 --- a/packages/camera/camera_android/example/lib/main.dart +++ b/packages/camera/camera_android/example/lib/main.dart @@ -123,7 +123,7 @@ class _CameraExampleHomeState extends State if (state == AppLifecycleState.inactive) { cameraController.dispose(); } else if (state == AppLifecycleState.resumed) { - onNewCameraSelected(cameraController.description); + _initializeCameraController(cameraController.description); } } @@ -603,10 +603,7 @@ class _CameraExampleHomeState extends State title: Icon(getCameraLensIcon(cameraDescription.lensDirection)), groupValue: controller?.description, value: cameraDescription, - onChanged: - controller != null && controller!.value.isRecordingVideo - ? null - : onChanged, + onChanged: onChanged, ), ), ); @@ -639,17 +636,15 @@ class _CameraExampleHomeState extends State } Future onNewCameraSelected(CameraDescription cameraDescription) async { - final CameraController? oldController = controller; - if (oldController != null) { - // `controller` needs to be set to null before getting disposed, - // to avoid a race condition when we use the controller that is being - // disposed. This happens when camera permission dialog shows up, - // which triggers `didChangeAppLifecycleState`, which disposes and - // re-creates the controller. - controller = null; - await oldController.dispose(); + if (controller != null) { + return controller!.setDescription(cameraDescription); + } else { + return _initializeCameraController(cameraDescription); } + } + Future _initializeCameraController( + CameraDescription cameraDescription) async { final CameraController cameraController = CameraController( cameraDescription, kIsWeb ? ResolutionPreset.max : ResolutionPreset.medium, diff --git a/packages/camera/camera_android/example/pubspec.yaml b/packages/camera/camera_android/example/pubspec.yaml index e23e31a886de..08f94ced1f31 100644 --- a/packages/camera/camera_android/example/pubspec.yaml +++ b/packages/camera/camera_android/example/pubspec.yaml @@ -14,7 +14,7 @@ dependencies: # The example app is bundled with the plugin so we use a path dependency on # the parent directory to use the current plugin's version. path: ../ - camera_platform_interface: ^2.3.1 + camera_platform_interface: ^2.4.0 flutter: sdk: flutter path_provider: ^2.0.0 @@ -32,3 +32,4 @@ dev_dependencies: flutter: uses-material-design: true + diff --git a/packages/camera/camera_android/lib/src/android_camera.dart b/packages/camera/camera_android/lib/src/android_camera.dart index 9ab9b578616a..eca1003247c6 100644 --- a/packages/camera/camera_android/lib/src/android_camera.dart +++ b/packages/camera/camera_android/lib/src/android_camera.dart @@ -505,6 +505,17 @@ class AndroidCamera extends CameraPlatform { ); } + @override + Future setDescriptionWhileRecording( + CameraDescription description) async { + await _channel.invokeMethod( + 'setDescriptionWhileRecording', + { + 'cameraName': description.name, + }, + ); + } + @override Widget buildPreview(int cameraId) { return Texture(textureId: cameraId); diff --git a/packages/camera/camera_android/pubspec.yaml b/packages/camera/camera_android/pubspec.yaml index fb3371912911..637658f4e691 100644 --- a/packages/camera/camera_android/pubspec.yaml +++ b/packages/camera/camera_android/pubspec.yaml @@ -2,7 +2,7 @@ name: camera_android description: Android implementation of the camera plugin. repository: https://github.com/flutter/plugins/tree/main/packages/camera/camera_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22 -version: 0.10.4 +version: 0.10.5 environment: sdk: ">=2.14.0 <3.0.0" @@ -18,7 +18,7 @@ flutter: dartPluginClass: AndroidCamera dependencies: - camera_platform_interface: ^2.3.1 + camera_platform_interface: ^2.4.0 flutter: sdk: flutter flutter_plugin_android_lifecycle: ^2.0.2 diff --git a/packages/camera/camera_android/test/android_camera_test.dart b/packages/camera/camera_android/test/android_camera_test.dart index d80bd9cac7a3..b56aa4e352aa 100644 --- a/packages/camera/camera_android/test/android_camera_test.dart +++ b/packages/camera/camera_android/test/android_camera_test.dart @@ -700,6 +700,29 @@ void main() { ]); }); + test('Should set the description while recording', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: _channelName, + methods: {'setDescriptionWhileRecording': null}, + ); + const CameraDescription camera2Description = CameraDescription( + name: 'Test2', + lensDirection: CameraLensDirection.front, + sensorOrientation: 0); + + // Act + await camera.setDescriptionWhileRecording(camera2Description); + + // Assert + expect(channel.log, [ + isMethodCall('setDescriptionWhileRecording', + arguments: { + 'cameraName': camera2Description.name, + }), + ]); + }); + test('Should set the flash mode', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( diff --git a/packages/camera/camera_avfoundation/CHANGELOG.md b/packages/camera/camera_avfoundation/CHANGELOG.md index f0605b7914cc..169596fa647e 100644 --- a/packages/camera/camera_avfoundation/CHANGELOG.md +++ b/packages/camera/camera_avfoundation/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.9.12 + +* Allows camera to be switched while video recording. + ## 0.9.11 * Adds back use of Optional type. diff --git a/packages/camera/camera_avfoundation/example/integration_test/camera_test.dart b/packages/camera/camera_avfoundation/example/integration_test/camera_test.dart index 34d460d44ec7..5a6935a90114 100644 --- a/packages/camera/camera_avfoundation/example/integration_test/camera_test.dart +++ b/packages/camera/camera_avfoundation/example/integration_test/camera_test.dart @@ -198,6 +198,51 @@ void main() { expect(duration, lessThan(recordingTime - timePaused)); }); + testWidgets('Set description while recording', (WidgetTester tester) async { + final List cameras = + await CameraPlatform.instance.availableCameras(); + if (cameras.length < 2) { + return; + } + + final CameraController controller = CameraController( + cameras[0], + ResolutionPreset.low, + enableAudio: false, + ); + + await controller.initialize(); + await controller.prepareForVideoRecording(); + + await controller.startVideoRecording(); + sleep(const Duration(milliseconds: 500)); + await controller.setDescription(cameras[1]); + sleep(const Duration(milliseconds: 500)); + + expect(controller.description, cameras[1]); + }); + + testWidgets('Set description', (WidgetTester tester) async { + final List cameras = + await CameraPlatform.instance.availableCameras(); + if (cameras.length < 2) { + return; + } + + final CameraController controller = CameraController( + cameras[0], + ResolutionPreset.low, + enableAudio: false, + ); + + await controller.initialize(); + sleep(const Duration(milliseconds: 500)); + await controller.setDescription(cameras[1]); + sleep(const Duration(milliseconds: 500)); + + expect(controller.description, cameras[1]); + }); + /// Start streaming with specifying the ImageFormatGroup. Future startStreaming(List cameras, ImageFormatGroup? imageFormatGroup) async { diff --git a/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj b/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj index 03c80d79c578..c63d00860204 100644 --- a/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 50; objects = { /* Begin PBXBuildFile section */ diff --git a/packages/camera/camera_avfoundation/example/ios/Runner/Info.plist b/packages/camera/camera_avfoundation/example/ios/Runner/Info.plist index ff2e341a1803..c50ce989f0c2 100644 --- a/packages/camera/camera_avfoundation/example/ios/Runner/Info.plist +++ b/packages/camera/camera_avfoundation/example/ios/Runner/Info.plist @@ -52,5 +52,7 @@ UIViewControllerBasedStatusBarAppearance + CADisableMinimumFrameDurationOnPhone + diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m index 0ae4887eb631..b42aa34e2a17 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m @@ -11,15 +11,20 @@ OCMStub([inputMock deviceInputWithDevice:[OCMArg any] error:[OCMArg setTo:nil]]) .andReturn(inputMock); - id sessionMock = OCMClassMock([AVCaptureSession class]); - OCMStub([sessionMock addInputWithNoConnections:[OCMArg any]]); // no-op - OCMStub([sessionMock canSetSessionPreset:[OCMArg any]]).andReturn(YES); + id videoSessionMock = OCMClassMock([AVCaptureSession class]); + OCMStub([videoSessionMock addInputWithNoConnections:[OCMArg any]]); // no-op + OCMStub([videoSessionMock canSetSessionPreset:[OCMArg any]]).andReturn(YES); + + id audioSessionMock = OCMClassMock([AVCaptureSession class]); + OCMStub([audioSessionMock addInputWithNoConnections:[OCMArg any]]); // no-op + OCMStub([audioSessionMock canSetSessionPreset:[OCMArg any]]).andReturn(YES); return [[FLTCam alloc] initWithCameraName:@"camera" resolutionPreset:@"medium" enableAudio:true orientation:UIDeviceOrientationPortrait - captureSession:sessionMock + videoCaptureSession:videoSessionMock + audioCaptureSession:audioSessionMock captureSessionQueue:captureSessionQueue error:nil]; } diff --git a/packages/camera/camera_avfoundation/example/lib/camera_controller.dart b/packages/camera/camera_avfoundation/example/lib/camera_controller.dart index 524186816aab..6e1804328d52 100644 --- a/packages/camera/camera_avfoundation/example/lib/camera_controller.dart +++ b/packages/camera/camera_avfoundation/example/lib/camera_controller.dart @@ -24,6 +24,7 @@ class CameraValue { required this.exposureMode, required this.focusMode, required this.deviceOrientation, + required this.description, this.lockedCaptureOrientation, this.recordingOrientation, this.isPreviewPaused = false, @@ -31,7 +32,7 @@ class CameraValue { }); /// Creates a new camera controller state for an uninitialized controller. - const CameraValue.uninitialized() + const CameraValue.uninitialized(CameraDescription description) : this( isInitialized: false, isRecordingVideo: false, @@ -43,6 +44,7 @@ class CameraValue { focusMode: FocusMode.auto, deviceOrientation: DeviceOrientation.portraitUp, isPreviewPaused: false, + description: description, ); /// True after [CameraController.initialize] has completed successfully. @@ -92,6 +94,9 @@ class CameraValue { /// The orientation of the currently running video recording. final DeviceOrientation? recordingOrientation; + /// The properties of the camera device controlled by this controller. + final CameraDescription description; + /// Creates a modified copy of the object. /// /// Explicitly specified fields get the specified value, all other fields get @@ -112,6 +117,7 @@ class CameraValue { Optional? lockedCaptureOrientation, Optional? recordingOrientation, bool? isPreviewPaused, + CameraDescription? description, Optional? previewPauseOrientation, }) { return CameraValue( @@ -132,6 +138,7 @@ class CameraValue { ? this.recordingOrientation : recordingOrientation.orNull, isPreviewPaused: isPreviewPaused ?? this.isPreviewPaused, + description: description ?? this.description, previewPauseOrientation: previewPauseOrientation == null ? this.previewPauseOrientation : previewPauseOrientation.orNull, @@ -165,14 +172,14 @@ class CameraValue { class CameraController extends ValueNotifier { /// Creates a new camera controller in an uninitialized state. CameraController( - this.description, + CameraDescription cameraDescription, this.resolutionPreset, { this.enableAudio = true, this.imageFormatGroup, - }) : super(const CameraValue.uninitialized()); + }) : super(CameraValue.uninitialized(cameraDescription)); /// The properties of the camera device controlled by this controller. - final CameraDescription description; + CameraDescription get description => value.description; /// The resolution this controller is targeting. /// @@ -202,7 +209,9 @@ class CameraController extends ValueNotifier { int get cameraId => _cameraId; /// Initializes the camera on the device. - Future initialize() async { + Future initialize() => _initializeWithDescription(description); + + Future _initializeWithDescription(CameraDescription description) async { final Completer initializeCompleter = Completer(); @@ -234,6 +243,7 @@ class CameraController extends ValueNotifier { value = value.copyWith( isInitialized: true, + description: description, previewSize: await initializeCompleter.future .then((CameraInitializedEvent event) => Size( event.previewWidth, @@ -274,6 +284,16 @@ class CameraController extends ValueNotifier { previewPauseOrientation: const Optional.absent()); } + /// Sets the description of the camera + Future setDescription(CameraDescription description) async { + if (value.isRecordingVideo) { + await CameraPlatform.instance.setDescriptionWhileRecording(description); + value = value.copyWith(description: description); + } else { + await _initializeWithDescription(description); + } + } + /// Captures an image and returns the file where it was saved. /// /// Throws a [CameraException] if the capture fails. diff --git a/packages/camera/camera_avfoundation/example/lib/main.dart b/packages/camera/camera_avfoundation/example/lib/main.dart index 4d98aed9a4c2..3731325a49fd 100644 --- a/packages/camera/camera_avfoundation/example/lib/main.dart +++ b/packages/camera/camera_avfoundation/example/lib/main.dart @@ -123,7 +123,7 @@ class _CameraExampleHomeState extends State if (state == AppLifecycleState.inactive) { cameraController.dispose(); } else if (state == AppLifecycleState.resumed) { - onNewCameraSelected(cameraController.description); + _initializeCameraController(cameraController.description); } } @@ -603,10 +603,7 @@ class _CameraExampleHomeState extends State title: Icon(getCameraLensIcon(cameraDescription.lensDirection)), groupValue: controller?.description, value: cameraDescription, - onChanged: - controller != null && controller!.value.isRecordingVideo - ? null - : onChanged, + onChanged: onChanged, ), ), ); @@ -639,17 +636,15 @@ class _CameraExampleHomeState extends State } Future onNewCameraSelected(CameraDescription cameraDescription) async { - final CameraController? oldController = controller; - if (oldController != null) { - // `controller` needs to be set to null before getting disposed, - // to avoid a race condition when we use the controller that is being - // disposed. This happens when camera permission dialog shows up, - // which triggers `didChangeAppLifecycleState`, which disposes and - // re-creates the controller. - controller = null; - await oldController.dispose(); + if (controller != null) { + return controller!.setDescription(cameraDescription); + } else { + return _initializeCameraController(cameraDescription); } + } + Future _initializeCameraController( + CameraDescription cameraDescription) async { final CameraController cameraController = CameraController( cameraDescription, kIsWeb ? ResolutionPreset.max : ResolutionPreset.medium, diff --git a/packages/camera/camera_avfoundation/example/pubspec.yaml b/packages/camera/camera_avfoundation/example/pubspec.yaml index 7c85ba807193..872a22021d2e 100644 --- a/packages/camera/camera_avfoundation/example/pubspec.yaml +++ b/packages/camera/camera_avfoundation/example/pubspec.yaml @@ -14,7 +14,7 @@ dependencies: # The example app is bundled with the plugin so we use a path dependency on # the parent directory to use the current plugin's version. path: ../ - camera_platform_interface: ^2.2.0 + camera_platform_interface: ^2.4.0 flutter: sdk: flutter path_provider: ^2.0.0 diff --git a/packages/camera/camera_avfoundation/ios/Classes/CameraPlugin.m b/packages/camera/camera_avfoundation/ios/Classes/CameraPlugin.m index b85f68d1f957..f9b2a911b67d 100644 --- a/packages/camera/camera_avfoundation/ios/Classes/CameraPlugin.m +++ b/packages/camera/camera_avfoundation/ios/Classes/CameraPlugin.m @@ -261,6 +261,8 @@ - (void)handleMethodCallAsync:(FlutterMethodCall *)call [_camera pausePreviewWithResult:result]; } else if ([@"resumePreview" isEqualToString:call.method]) { [_camera resumePreviewWithResult:result]; + } else if ([@"setDescriptionWhileRecording" isEqualToString:call.method]) { + [_camera setDescriptionWhileRecording:(call.arguments[@"cameraName"]) result:result]; } else { [result sendNotImplemented]; } diff --git a/packages/camera/camera_avfoundation/ios/Classes/FLTCam.h b/packages/camera/camera_avfoundation/ios/Classes/FLTCam.h index 85b8e2ae06f2..df2a155855dd 100644 --- a/packages/camera/camera_avfoundation/ios/Classes/FLTCam.h +++ b/packages/camera/camera_avfoundation/ios/Classes/FLTCam.h @@ -95,6 +95,8 @@ NS_ASSUME_NONNULL_BEGIN - (void)applyFocusMode:(FLTFocusMode)focusMode onDevice:(AVCaptureDevice *)captureDevice; - (void)pausePreviewWithResult:(FLTThreadSafeFlutterResult *)result; - (void)resumePreviewWithResult:(FLTThreadSafeFlutterResult *)result; +- (void)setDescriptionWhileRecording:(NSString *)cameraName + result:(FLTThreadSafeFlutterResult *)result; - (void)setExposurePointWithResult:(FLTThreadSafeFlutterResult *)result x:(double)x y:(double)y; - (void)setFocusPointWithResult:(FLTThreadSafeFlutterResult *)result x:(double)x y:(double)y; - (void)setExposureOffsetWithResult:(FLTThreadSafeFlutterResult *)result offset:(double)offset; diff --git a/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m b/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m index a7d6cd24be3c..d5247e00382e 100644 --- a/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m +++ b/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m @@ -43,7 +43,8 @@ @interface FLTCam () setDescriptionWhileRecording( + CameraDescription description) async { + await _channel.invokeMethod( + 'setDescriptionWhileRecording', + { + 'cameraName': description.name, + }, + ); + } + @override Widget buildPreview(int cameraId) { return Texture(textureId: cameraId); diff --git a/packages/camera/camera_avfoundation/pubspec.yaml b/packages/camera/camera_avfoundation/pubspec.yaml index b272a4c5c68d..78c9156a7b79 100644 --- a/packages/camera/camera_avfoundation/pubspec.yaml +++ b/packages/camera/camera_avfoundation/pubspec.yaml @@ -2,7 +2,7 @@ name: camera_avfoundation description: iOS implementation of the camera plugin. repository: https://github.com/flutter/plugins/tree/main/packages/camera/camera_avfoundation issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22 -version: 0.9.11 +version: 0.9.12 environment: sdk: ">=2.14.0 <3.0.0" @@ -17,7 +17,7 @@ flutter: dartPluginClass: AVFoundationCamera dependencies: - camera_platform_interface: ^2.3.1 + camera_platform_interface: ^2.4.0 flutter: sdk: flutter stream_transform: ^2.0.0 diff --git a/packages/camera/camera_avfoundation/test/avfoundation_camera_test.dart b/packages/camera/camera_avfoundation/test/avfoundation_camera_test.dart index 5d0b74cf0c0c..e756f38ff122 100644 --- a/packages/camera/camera_avfoundation/test/avfoundation_camera_test.dart +++ b/packages/camera/camera_avfoundation/test/avfoundation_camera_test.dart @@ -701,6 +701,29 @@ void main() { ]); }); + test('Should set the description while recording', () async { + // Arrange + final MethodChannelMock channel = MethodChannelMock( + channelName: _channelName, + methods: {'setDescriptionWhileRecording': null}, + ); + const CameraDescription camera2Description = CameraDescription( + name: 'Test2', + lensDirection: CameraLensDirection.front, + sensorOrientation: 0); + + // Act + await camera.setDescriptionWhileRecording(camera2Description); + + // Assert + expect(channel.log, [ + isMethodCall('setDescriptionWhileRecording', + arguments: { + 'cameraName': camera2Description.name, + }), + ]); + }); + test('Should set the flash mode', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( From 66d5724fa09639fb62ef4164850aecad96e0fe63 Mon Sep 17 00:00:00 2001 From: stuartmorgan Date: Tue, 14 Feb 2023 10:31:37 -0800 Subject: [PATCH 099/130] Revert "[camera] flip/change camera while recording (split out PR for cam_avfoundation and cam_android) (#7109)" (#7181) This reverts commit 9c312d4d2f5fe37608e11fa68295ecb59b2a200e. --- packages/camera/camera_android/CHANGELOG.md | 4 - .../io/flutter/plugins/camera/Camera.java | 167 ++------ .../plugins/camera/MethodCallHandlerImpl.java | 12 - .../flutter/plugins/camera/VideoRenderer.java | 365 ------------------ .../io/flutter/plugins/camera/CameraTest.java | 109 ------ .../example/integration_test/camera_test.dart | 45 --- .../example/lib/camera_controller.dart | 30 +- .../camera_android/example/lib/main.dart | 23 +- .../camera_android/example/pubspec.yaml | 3 +- .../lib/src/android_camera.dart | 11 - packages/camera/camera_android/pubspec.yaml | 4 +- .../test/android_camera_test.dart | 23 -- .../camera/camera_avfoundation/CHANGELOG.md | 4 - .../example/integration_test/camera_test.dart | 45 --- .../ios/Runner.xcodeproj/project.pbxproj | 2 +- .../example/ios/Runner/Info.plist | 2 - .../example/ios/RunnerTests/CameraTestUtils.m | 13 +- .../example/lib/camera_controller.dart | 30 +- .../camera_avfoundation/example/lib/main.dart | 23 +- .../camera_avfoundation/example/pubspec.yaml | 2 +- .../ios/Classes/CameraPlugin.m | 2 - .../camera_avfoundation/ios/Classes/FLTCam.h | 2 - .../camera_avfoundation/ios/Classes/FLTCam.m | 177 +++------ .../ios/Classes/FLTCam_Test.h | 3 +- .../lib/src/avfoundation_camera.dart | 11 - .../camera/camera_avfoundation/pubspec.yaml | 4 +- .../test/avfoundation_camera_test.dart | 23 -- 27 files changed, 125 insertions(+), 1014 deletions(-) delete mode 100644 packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/VideoRenderer.java diff --git a/packages/camera/camera_android/CHANGELOG.md b/packages/camera/camera_android/CHANGELOG.md index f7f0b2a0343a..4609b402058a 100644 --- a/packages/camera/camera_android/CHANGELOG.md +++ b/packages/camera/camera_android/CHANGELOG.md @@ -1,7 +1,3 @@ -## 0.10.5 - -* Allows camera to be switched while video recording. - ## 0.10.4 * Temporarily fixes issue with requested video profiles being null by falling back to deprecated behavior in that case. diff --git a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/Camera.java b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/Camera.java index c2255e23273a..b02d6864b5b7 100644 --- a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/Camera.java +++ b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/Camera.java @@ -115,28 +115,13 @@ class Camera * Holds all of the camera features/settings and will be used to update the request builder when * one changes. */ - private CameraFeatures cameraFeatures; - - private String imageFormatGroup; - - /** - * Takes an input/output surface and orients the recording correctly. This is needed because - * switching cameras while recording causes the wrong orientation. - */ - private VideoRenderer videoRenderer; - - /** - * Whether or not the camera aligns with the initial way the camera was facing if the camera was - * flipped. - */ - private int initialCameraFacing; + private final CameraFeatures cameraFeatures; private final SurfaceTextureEntry flutterTexture; - private final ResolutionPreset resolutionPreset; private final boolean enableAudio; private final Context applicationContext; private final DartMessenger dartMessenger; - private CameraProperties cameraProperties; + private final CameraProperties cameraProperties; private final CameraFeatureFactory cameraFeatureFactory; private final Activity activity; /** A {@link CameraCaptureSession.CaptureCallback} that handles events related to JPEG capture. */ @@ -226,7 +211,6 @@ public Camera( this.applicationContext = activity.getApplicationContext(); this.cameraProperties = cameraProperties; this.cameraFeatureFactory = cameraFeatureFactory; - this.resolutionPreset = resolutionPreset; this.cameraFeatures = CameraFeatures.init( cameraFeatureFactory, cameraProperties, activity, dartMessenger, resolutionPreset); @@ -267,7 +251,6 @@ private void prepareMediaRecorder(String outputFilePath) throws IOException { if (mediaRecorder != null) { mediaRecorder.release(); } - closeRenderer(); final PlatformChannel.DeviceOrientation lockedOrientation = ((SensorOrientationFeature) cameraFeatures.getSensorOrientation()) @@ -296,7 +279,6 @@ private void prepareMediaRecorder(String outputFilePath) throws IOException { @SuppressLint("MissingPermission") public void open(String imageFormatGroup) throws CameraAccessException { - this.imageFormatGroup = imageFormatGroup; final ResolutionFeature resolutionFeature = cameraFeatures.getResolution(); if (!resolutionFeature.checkIsSupported()) { @@ -341,16 +323,14 @@ public void onOpened(@NonNull CameraDevice device) { cameraDevice = new DefaultCameraDeviceWrapper(device); try { startPreview(); - if (!recordingVideo) // only send initialization if we werent already recording and switching cameras dartMessenger.sendCameraInitializedEvent( - resolutionFeature.getPreviewSize().getWidth(), - resolutionFeature.getPreviewSize().getHeight(), - cameraFeatures.getExposureLock().getValue(), - cameraFeatures.getAutoFocus().getValue(), - cameraFeatures.getExposurePoint().checkIsSupported(), - cameraFeatures.getFocusPoint().checkIsSupported()); - - } catch (CameraAccessException | InterruptedException e) { + resolutionFeature.getPreviewSize().getWidth(), + resolutionFeature.getPreviewSize().getHeight(), + cameraFeatures.getExposureLock().getValue(), + cameraFeatures.getAutoFocus().getValue(), + cameraFeatures.getExposurePoint().checkIsSupported(), + cameraFeatures.getFocusPoint().checkIsSupported()); + } catch (CameraAccessException e) { dartMessenger.sendCameraErrorEvent(e.getMessage()); close(); } @@ -360,8 +340,7 @@ public void onOpened(@NonNull CameraDevice device) { public void onClosed(@NonNull CameraDevice camera) { Log.i(TAG, "open | onClosed"); - // Prevents calls to methods that would otherwise result in IllegalStateException - // exceptions. + // Prevents calls to methods that would otherwise result in IllegalStateException exceptions. cameraDevice = null; closeCaptureSession(); dartMessenger.sendCameraClosingEvent(); @@ -777,7 +756,7 @@ public void startVideoRecording( if (imageStreamChannel != null) { setStreamHandler(imageStreamChannel); } - initialCameraFacing = cameraProperties.getLensFacing(); + recordingVideo = true; try { startCapture(true, imageStreamChannel != null); @@ -789,13 +768,6 @@ public void startVideoRecording( } } - private void closeRenderer() { - if (videoRenderer != null) { - videoRenderer.close(); - videoRenderer = null; - } - } - public void stopVideoRecording(@NonNull final Result result) { if (!recordingVideo) { result.success(null); @@ -806,7 +778,6 @@ public void stopVideoRecording(@NonNull final Result result) { cameraFeatureFactory.createAutoFocusFeature(cameraProperties, false)); recordingVideo = false; try { - closeRenderer(); captureSession.abortCaptures(); mediaRecorder.stop(); } catch (CameraAccessException | IllegalStateException e) { @@ -815,7 +786,7 @@ public void stopVideoRecording(@NonNull final Result result) { mediaRecorder.reset(); try { startPreview(); - } catch (CameraAccessException | IllegalStateException | InterruptedException e) { + } catch (CameraAccessException | IllegalStateException e) { result.error("videoRecordingFailed", e.getMessage(), null); return; } @@ -1099,49 +1070,11 @@ public void resumePreview() { null, (code, message) -> dartMessenger.sendCameraErrorEvent(message)); } - public void startPreview() throws CameraAccessException, InterruptedException { - // If recording is already in progress, the camera is being flipped, so send it through the VideoRenderer to keep the correct orientation. - if (recordingVideo) { - startPreviewWithVideoRendererStream(); - } else { - startRegularPreview(); - } - } - - private void startRegularPreview() throws CameraAccessException { + public void startPreview() throws CameraAccessException { if (pictureImageReader == null || pictureImageReader.getSurface() == null) return; Log.i(TAG, "startPreview"); - createCaptureSession(CameraDevice.TEMPLATE_PREVIEW, pictureImageReader.getSurface()); - } - private void startPreviewWithVideoRendererStream() - throws CameraAccessException, InterruptedException { - if (videoRenderer == null) return; - - // get rotation for rendered video - final PlatformChannel.DeviceOrientation lockedOrientation = - ((SensorOrientationFeature) cameraFeatures.getSensorOrientation()) - .getLockedCaptureOrientation(); - DeviceOrientationManager orientationManager = - cameraFeatures.getSensorOrientation().getDeviceOrientationManager(); - - int rotation = 0; - if (orientationManager != null) { - rotation = - lockedOrientation == null - ? orientationManager.getVideoOrientation() - : orientationManager.getVideoOrientation(lockedOrientation); - } - - if (cameraProperties.getLensFacing() != initialCameraFacing) { - - // If the new camera is facing the opposite way than the initial recording, - // the rotation should be flipped 180 degrees. - rotation = (rotation + 180) % 360; - } - videoRenderer.setRotation(rotation); - - createCaptureSession(CameraDevice.TEMPLATE_RECORD, videoRenderer.getInputSurface()); + createCaptureSession(CameraDevice.TEMPLATE_PREVIEW, pictureImageReader.getSurface()); } public void startPreviewWithImageStream(EventChannel imageStreamChannel) @@ -1267,7 +1200,17 @@ private void closeCaptureSession() { public void close() { Log.i(TAG, "close"); - stopAndReleaseCamera(); + if (cameraDevice != null) { + cameraDevice.close(); + cameraDevice = null; + + // Closing the CameraDevice without closing the CameraCaptureSession is recommended + // for quickly closing the camera: + // https://developer.android.com/reference/android/hardware/camera2/CameraCaptureSession#close() + captureSession = null; + } else { + closeCaptureSession(); + } if (pictureImageReader != null) { pictureImageReader.close(); @@ -1286,66 +1229,6 @@ public void close() { stopBackgroundThread(); } - private void stopAndReleaseCamera() { - if (cameraDevice != null) { - cameraDevice.close(); - cameraDevice = null; - - // Closing the CameraDevice without closing the CameraCaptureSession is recommended - // for quickly closing the camera: - // https://developer.android.com/reference/android/hardware/camera2/CameraCaptureSession#close() - captureSession = null; - } else { - closeCaptureSession(); - } - } - - private void prepareVideoRenderer() { - if (videoRenderer != null) return; - final ResolutionFeature resolutionFeature = cameraFeatures.getResolution(); - - // handle videoRenderer errors - Thread.UncaughtExceptionHandler videoRendererUncaughtExceptionHandler = - new Thread.UncaughtExceptionHandler() { - @Override - public void uncaughtException(Thread thread, Throwable ex) { - dartMessenger.sendCameraErrorEvent( - "Failed to process frames after camera was flipped."); - } - }; - - videoRenderer = - new VideoRenderer( - mediaRecorder.getSurface(), - resolutionFeature.getCaptureSize().getWidth(), - resolutionFeature.getCaptureSize().getHeight(), - videoRendererUncaughtExceptionHandler); - } - - public void setDescriptionWhileRecording( - @NonNull final Result result, CameraProperties properties) { - - if (!recordingVideo) { - result.error("setDescriptionWhileRecordingFailed", "Device was not recording", null); - return; - } - - stopAndReleaseCamera(); - prepareVideoRenderer(); - cameraProperties = properties; - cameraFeatures = - CameraFeatures.init( - cameraFeatureFactory, cameraProperties, activity, dartMessenger, resolutionPreset); - cameraFeatures.setAutoFocus( - cameraFeatureFactory.createAutoFocusFeature(cameraProperties, true)); - try { - open(imageFormatGroup); - } catch (CameraAccessException e) { - result.error("setDescriptionWhileRecordingFailed", e.getMessage(), null); - } - result.success(null); - } - public void dispose() { Log.i(TAG, "dispose"); diff --git a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java index aad62bbaba85..432344ade8cd 100644 --- a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java +++ b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java @@ -354,18 +354,6 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result) result.success(null); break; } - case "setDescriptionWhileRecording": - { - try { - String cameraName = call.argument("cameraName"); - CameraProperties cameraProperties = - new CameraPropertiesImpl(cameraName, CameraUtils.getCameraManager(activity)); - camera.setDescriptionWhileRecording(result, cameraProperties); - } catch (Exception e) { - handleException(e, result); - } - break; - } case "dispose": { if (camera != null) { diff --git a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/VideoRenderer.java b/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/VideoRenderer.java deleted file mode 100644 index b7128373b101..000000000000 --- a/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/VideoRenderer.java +++ /dev/null @@ -1,365 +0,0 @@ -// Copyright 2013 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.camera; - -import static android.os.SystemClock.uptimeMillis; - -import android.graphics.SurfaceTexture; -import android.opengl.EGL14; -import android.opengl.EGLConfig; -import android.opengl.EGLContext; -import android.opengl.EGLDisplay; -import android.opengl.EGLExt; -import android.opengl.EGLSurface; -import android.opengl.GLES11Ext; -import android.opengl.GLES20; -import android.opengl.GLUtils; -import android.opengl.Matrix; -import android.os.Handler; -import android.os.HandlerThread; -import android.util.Log; -import android.view.Surface; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; - -/** - * Renders video onto texture after performing a matrix rotation on each frame. - * - *

VideoRenderer is needed because when switching between cameras mid recording, the orientation - * of the recording from the new camera usually becomes flipped. MediaRecorder has - * setOrientationHint, but that cannot be called mid recording and therefore isn't useful. Android - * Camera2 has no setDisplayOrientation on the camera itself as it is supposed to 'just work' (see - * https://stackoverflow.com/questions/33479004/what-is-the-camera2-api-equivalent-of-setdisplayorientation). - * Therefore it cannot be used to set the camera's orientation either. - * - *

This leaves the solution to be routing the recording through a surface texture and performing - * a matrix transformation on it manually to get the correct orientation. This only happens when - * setDescription is called mid video recording. - */ -public class VideoRenderer { - - private static String TAG = "VideoRenderer"; - - private static final String vertexShaderCode = - " precision highp float;\n" - + " attribute vec3 vertexPosition;\n" - + " attribute vec2 uvs;\n" - + " varying vec2 varUvs;\n" - + " uniform mat4 texMatrix;\n" - + " uniform mat4 mvp;\n" - + "\n" - + " void main()\n" - + " {\n" - + " varUvs = (texMatrix * vec4(uvs.x, uvs.y, 0, 1.0)).xy;\n" - + " gl_Position = mvp * vec4(vertexPosition, 1.0);\n" - + " }"; - - private static final String fragmentShaderCode = - " #extension GL_OES_EGL_image_external : require\n" - + " precision mediump float;\n" - + "\n" - + " varying vec2 varUvs;\n" - + " uniform samplerExternalOES texSampler;\n" - + "\n" - + " void main()\n" - + " {\n" - + " vec4 c = texture2D(texSampler, varUvs);\n" - + " gl_FragColor = vec4(c.r, c.g, c.b, c.a);\n" - + " }"; - - private final int[] textureHandles = new int[1]; - - private final float[] vertices = - new float[] { - -1.0f, -1.0f, 0.0f, 0f, 0f, -1.0f, 1.0f, 0.0f, 0f, 1f, 1.0f, 1.0f, 0.0f, 1f, 1f, 1.0f, - -1.0f, 0.0f, 1f, 0f - }; - - private final int[] indices = new int[] {2, 1, 0, 0, 3, 2}; - - private int program; - private int vertexHandle = 0; - private final int[] bufferHandles = new int[2]; - private int uvsHandle = 0; - private int texMatrixHandle = 0; - private int mvpHandle = 0; - - EGLDisplay display; - EGLContext context; - EGLSurface surface; - private Thread thread; - private final Surface outputSurface; - private SurfaceTexture inputSurfaceTexture; - private Surface inputSurface; - - private HandlerThread surfaceTextureFrameAvailableHandler; - private final Object surfaceTextureAvailableFrameLock = new Object(); - private Boolean surfaceTextureFrameAvailable = false; - - private final int recordingWidth; - private final int recordingHeight; - private int rotation = 0; - - private final Object lock = new Object(); - - private final Thread.UncaughtExceptionHandler uncaughtExceptionHandler; - - /** Gets surface for input. Blocks until surface is ready. */ - public Surface getInputSurface() throws InterruptedException { - synchronized (lock) { - while (inputSurface == null) { - lock.wait(); - } - } - return inputSurface; - } - - public VideoRenderer( - Surface outputSurface, - int recordingWidth, - int recordingHeight, - Thread.UncaughtExceptionHandler uncaughtExceptionHandler) { - this.outputSurface = outputSurface; - this.recordingHeight = recordingHeight; - this.recordingWidth = recordingWidth; - this.uncaughtExceptionHandler = uncaughtExceptionHandler; - startOpenGL(); - Log.d(TAG, "VideoRenderer setup complete"); - } - - /** Stop rendering and cleanup resources. */ - public void close() { - thread.interrupt(); - surfaceTextureFrameAvailableHandler.quitSafely(); - cleanupOpenGL(); - inputSurfaceTexture.release(); - } - - private void cleanupOpenGL() { - GLES20.glDeleteBuffers(2, bufferHandles, 0); - GLES20.glDeleteTextures(1, textureHandles, 0); - EGL14.eglDestroyContext(display, context); - EGL14.eglDestroySurface(display, surface); - GLES20.glDeleteProgram(program); - } - - /** Configures openGL. Must be called in same thread as draw is called. */ - private void configureOpenGL() { - synchronized (lock) { - display = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY); - if (display == EGL14.EGL_NO_DISPLAY) - throw new RuntimeException( - "eglDisplay == EGL14.EGL_NO_DISPLAY: " - + GLUtils.getEGLErrorString(EGL14.eglGetError())); - - int[] version = new int[2]; - if (!EGL14.eglInitialize(display, version, 0, version, 1)) - throw new RuntimeException( - "eglInitialize(): " + GLUtils.getEGLErrorString(EGL14.eglGetError())); - - String eglExtensions = EGL14.eglQueryString(display, EGL14.EGL_EXTENSIONS); - if (!eglExtensions.contains("EGL_ANDROID_presentation_time")) - throw new RuntimeException( - "cannot configure OpenGL. missing EGL_ANDROID_presentation_time"); - - int[] attribList = - new int[] { - EGL14.EGL_RED_SIZE, 8, - EGL14.EGL_GREEN_SIZE, 8, - EGL14.EGL_BLUE_SIZE, 8, - EGL14.EGL_ALPHA_SIZE, 8, - EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT, - EGLExt.EGL_RECORDABLE_ANDROID, 1, - EGL14.EGL_NONE - }; - - EGLConfig[] configs = new EGLConfig[1]; - int[] numConfigs = new int[1]; - if (!EGL14.eglChooseConfig(display, attribList, 0, configs, 0, configs.length, numConfigs, 0)) - throw new RuntimeException(GLUtils.getEGLErrorString(EGL14.eglGetError())); - - int err = EGL14.eglGetError(); - if (err != EGL14.EGL_SUCCESS) throw new RuntimeException(GLUtils.getEGLErrorString(err)); - - int[] ctxAttribs = new int[] {EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL14.EGL_NONE}; - context = EGL14.eglCreateContext(display, configs[0], EGL14.EGL_NO_CONTEXT, ctxAttribs, 0); - - err = EGL14.eglGetError(); - if (err != EGL14.EGL_SUCCESS) throw new RuntimeException(GLUtils.getEGLErrorString(err)); - - int[] surfaceAttribs = new int[] {EGL14.EGL_NONE}; - - surface = EGL14.eglCreateWindowSurface(display, configs[0], outputSurface, surfaceAttribs, 0); - - err = EGL14.eglGetError(); - if (err != EGL14.EGL_SUCCESS) throw new RuntimeException(GLUtils.getEGLErrorString(err)); - - if (!EGL14.eglMakeCurrent(display, surface, surface, context)) - throw new RuntimeException( - "eglMakeCurrent(): " + GLUtils.getEGLErrorString(EGL14.eglGetError())); - - ByteBuffer vertexBuffer = ByteBuffer.allocateDirect(vertices.length * 4); - vertexBuffer.order(ByteOrder.nativeOrder()); - vertexBuffer.asFloatBuffer().put(vertices); - vertexBuffer.asFloatBuffer().position(0); - - ByteBuffer indexBuffer = ByteBuffer.allocateDirect(indices.length * 4); - indexBuffer.order(ByteOrder.nativeOrder()); - indexBuffer.asIntBuffer().put(indices); - indexBuffer.position(0); - - int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode); - int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode); - - program = GLES20.glCreateProgram(); - - GLES20.glAttachShader(program, vertexShader); - GLES20.glAttachShader(program, fragmentShader); - GLES20.glLinkProgram(program); - - deleteShader(vertexShader); - deleteShader(fragmentShader); - - vertexHandle = GLES20.glGetAttribLocation(program, "vertexPosition"); - uvsHandle = GLES20.glGetAttribLocation(program, "uvs"); - texMatrixHandle = GLES20.glGetUniformLocation(program, "texMatrix"); - mvpHandle = GLES20.glGetUniformLocation(program, "mvp"); - - // Initialize buffers - GLES20.glGenBuffers(2, bufferHandles, 0); - - GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, bufferHandles[0]); - GLES20.glBufferData( - GLES20.GL_ARRAY_BUFFER, vertices.length * 4, vertexBuffer, GLES20.GL_DYNAMIC_DRAW); - - GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, bufferHandles[1]); - GLES20.glBufferData( - GLES20.GL_ELEMENT_ARRAY_BUFFER, indices.length * 4, indexBuffer, GLES20.GL_DYNAMIC_DRAW); - - // Init texture that will receive decoded frames - GLES20.glGenTextures(1, textureHandles, 0); - GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureHandles[0]); - - inputSurfaceTexture = new SurfaceTexture(getTexId()); - inputSurfaceTexture.setDefaultBufferSize(recordingWidth, recordingHeight); - surfaceTextureFrameAvailableHandler = new HandlerThread("FrameHandlerThread"); - surfaceTextureFrameAvailableHandler.start(); - inputSurface = new Surface(inputSurfaceTexture); - - inputSurfaceTexture.setOnFrameAvailableListener( - new SurfaceTexture.OnFrameAvailableListener() { - @Override - public void onFrameAvailable(SurfaceTexture surfaceTexture) { - synchronized (surfaceTextureAvailableFrameLock) { - if (surfaceTextureFrameAvailable) - Log.w(TAG, "Frame available before processing other frames. dropping frames"); - surfaceTextureFrameAvailable = true; - surfaceTextureAvailableFrameLock.notifyAll(); - } - } - }, - new Handler(surfaceTextureFrameAvailableHandler.getLooper())); - lock.notifyAll(); - } - } - - /** Starts and configures Video Renderer. */ - private void startOpenGL() { - Log.d(TAG, "Starting OpenGL Thread"); - thread = - new Thread() { - @Override - public void run() { - - configureOpenGL(); - - try { - // Continuously pull frames from input surface texture and use videoRenderer to modify - // to correct rotation. - while (!Thread.interrupted()) { - - synchronized (surfaceTextureAvailableFrameLock) { - while (!surfaceTextureFrameAvailable) { - surfaceTextureAvailableFrameLock.wait(500); - } - surfaceTextureFrameAvailable = false; - } - - inputSurfaceTexture.updateTexImage(); - - float[] surfaceTextureMatrix = new float[16]; - inputSurfaceTexture.getTransformMatrix(surfaceTextureMatrix); - - draw(recordingWidth, recordingHeight, surfaceTextureMatrix); - } - } catch (InterruptedException e) { - Log.d(TAG, "thread interrupted while waiting for frames"); - } - } - }; - thread.setUncaughtExceptionHandler(uncaughtExceptionHandler); - thread.start(); - } - - public int getTexId() { - return textureHandles[0]; - } - - public float[] moveMatrix() { - float[] m = new float[16]; - Matrix.setIdentityM(m, 0); - Matrix.rotateM(m, 0, rotation, 0, 0, 1); - return m; - } - - public void setRotation(int rotation) { - this.rotation = rotation; - } - - private int loadShader(int type, String code) { - - int shader = GLES20.glCreateShader(type); - - GLES20.glShaderSource(shader, code); - GLES20.glCompileShader(shader); - return shader; - } - - private void deleteShader(int shader) { - GLES20.glDeleteShader(shader); - } - - public void draw(int viewportWidth, int viewportHeight, float[] texMatrix) { - - GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT); - GLES20.glClearColor(0f, 0f, 0f, 0f); - - GLES20.glViewport(0, 0, viewportWidth, viewportHeight); - - GLES20.glUseProgram(program); - - // Pass transformations to shader - GLES20.glUniformMatrix4fv(texMatrixHandle, 1, false, texMatrix, 0); - GLES20.glUniformMatrix4fv(mvpHandle, 1, false, moveMatrix(), 0); - - // Prepare buffers with vertices and indices & draw - GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, bufferHandles[0]); - GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, bufferHandles[1]); - - GLES20.glEnableVertexAttribArray(vertexHandle); - GLES20.glVertexAttribPointer(vertexHandle, 3, GLES20.GL_FLOAT, false, 4 * 5, 0); - - GLES20.glEnableVertexAttribArray(uvsHandle); - GLES20.glVertexAttribPointer(uvsHandle, 2, GLES20.GL_FLOAT, false, 4 * 5, 3 * 4); - - GLES20.glDrawElements(GLES20.GL_TRIANGLES, 6, GLES20.GL_UNSIGNED_INT, 0); - - EGLExt.eglPresentationTimeANDROID(display, surface, uptimeMillis() * 1000000); - if (!EGL14.eglSwapBuffers(display, surface)) { - throw new RuntimeException( - "eglSwapBuffers()" + GLUtils.getEGLErrorString(EGL14.eglGetError())); - } - } -} diff --git a/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraTest.java b/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraTest.java index 9a6f7dc20d22..9a679017ded2 100644 --- a/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraTest.java +++ b/packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraTest.java @@ -602,115 +602,6 @@ public void resumeVideoRecording_shouldCallPauseWhenRecordingAndOnAPIN() { verify(mockResult, never()).error(any(), any(), any()); } - @Test - public void setDescriptionWhileRecording() { - MethodChannel.Result mockResult = mock(MethodChannel.Result.class); - MediaRecorder mockMediaRecorder = mock(MediaRecorder.class); - VideoRenderer mockVideoRenderer = mock(VideoRenderer.class); - TestUtils.setPrivateField(camera, "mediaRecorder", mockMediaRecorder); - TestUtils.setPrivateField(camera, "recordingVideo", true); - TestUtils.setPrivateField(camera, "videoRenderer", mockVideoRenderer); - - final CameraProperties newCameraProperties = mock(CameraProperties.class); - camera.setDescriptionWhileRecording(mockResult, newCameraProperties); - - verify(mockResult, times(1)).success(null); - verify(mockResult, never()).error(any(), any(), any()); - } - - @Test - public void startPreview_shouldPullStreamFromVideoRenderer() - throws InterruptedException, CameraAccessException { - VideoRenderer mockVideoRenderer = mock(VideoRenderer.class); - ArrayList mockRequestBuilders = new ArrayList<>(); - mockRequestBuilders.add(mock(CaptureRequest.Builder.class)); - SurfaceTexture mockSurfaceTexture = mock(SurfaceTexture.class); - Size mockSize = mock(Size.class); - TestUtils.setPrivateField(camera, "recordingVideo", true); - TestUtils.setPrivateField(camera, "videoRenderer", mockVideoRenderer); - CameraDeviceWrapper fakeCamera = new FakeCameraDeviceWrapper(mockRequestBuilders); - TestUtils.setPrivateField(camera, "cameraDevice", fakeCamera); - - TextureRegistry.SurfaceTextureEntry cameraFlutterTexture = - (TextureRegistry.SurfaceTextureEntry) TestUtils.getPrivateField(camera, "flutterTexture"); - ResolutionFeature resolutionFeature = - (ResolutionFeature) - TestUtils.getPrivateField(mockCameraFeatureFactory, "mockResolutionFeature"); - - when(cameraFlutterTexture.surfaceTexture()).thenReturn(mockSurfaceTexture); - when(resolutionFeature.getPreviewSize()).thenReturn(mockSize); - - camera.startPreview(); - verify(mockVideoRenderer, times(1)) - .getInputSurface(); // stream pulled from videoRenderer's surface. - } - - @Test - public void startPreview_shouldPullStreamFromImageReader() - throws InterruptedException, CameraAccessException { - ArrayList mockRequestBuilders = new ArrayList<>(); - mockRequestBuilders.add(mock(CaptureRequest.Builder.class)); - SurfaceTexture mockSurfaceTexture = mock(SurfaceTexture.class); - Size mockSize = mock(Size.class); - ImageReader mockImageReader = mock(ImageReader.class); - TestUtils.setPrivateField(camera, "recordingVideo", false); - TestUtils.setPrivateField(camera, "pictureImageReader", mockImageReader); - CameraDeviceWrapper fakeCamera = new FakeCameraDeviceWrapper(mockRequestBuilders); - TestUtils.setPrivateField(camera, "cameraDevice", fakeCamera); - - TextureRegistry.SurfaceTextureEntry cameraFlutterTexture = - (TextureRegistry.SurfaceTextureEntry) TestUtils.getPrivateField(camera, "flutterTexture"); - ResolutionFeature resolutionFeature = - (ResolutionFeature) - TestUtils.getPrivateField(mockCameraFeatureFactory, "mockResolutionFeature"); - - when(cameraFlutterTexture.surfaceTexture()).thenReturn(mockSurfaceTexture); - when(resolutionFeature.getPreviewSize()).thenReturn(mockSize); - - camera.startPreview(); - verify(mockImageReader, times(1)) - .getSurface(); // stream pulled from regular imageReader's surface. - } - - @Test - public void startPreview_shouldFlipRotation() throws InterruptedException, CameraAccessException { - VideoRenderer mockVideoRenderer = mock(VideoRenderer.class); - ArrayList mockRequestBuilders = new ArrayList<>(); - mockRequestBuilders.add(mock(CaptureRequest.Builder.class)); - SurfaceTexture mockSurfaceTexture = mock(SurfaceTexture.class); - Size mockSize = mock(Size.class); - TestUtils.setPrivateField(camera, "recordingVideo", true); - TestUtils.setPrivateField(camera, "videoRenderer", mockVideoRenderer); - TestUtils.setPrivateField(camera, "initialCameraFacing", CameraMetadata.LENS_FACING_BACK); - CameraDeviceWrapper fakeCamera = new FakeCameraDeviceWrapper(mockRequestBuilders); - TestUtils.setPrivateField(camera, "cameraDevice", fakeCamera); - - TextureRegistry.SurfaceTextureEntry cameraFlutterTexture = - (TextureRegistry.SurfaceTextureEntry) TestUtils.getPrivateField(camera, "flutterTexture"); - ResolutionFeature resolutionFeature = - (ResolutionFeature) - TestUtils.getPrivateField(mockCameraFeatureFactory, "mockResolutionFeature"); - - when(cameraFlutterTexture.surfaceTexture()).thenReturn(mockSurfaceTexture); - when(resolutionFeature.getPreviewSize()).thenReturn(mockSize); - when(mockCameraProperties.getLensFacing()).thenReturn(CameraMetadata.LENS_FACING_FRONT); - - camera.startPreview(); - verify(mockVideoRenderer, times(1)).setRotation(180); - } - - @Test - public void setDescriptionWhileRecording_shouldErrorWhenNotRecording() { - MethodChannel.Result mockResult = mock(MethodChannel.Result.class); - TestUtils.setPrivateField(camera, "recordingVideo", false); - final CameraProperties newCameraProperties = mock(CameraProperties.class); - camera.setDescriptionWhileRecording(mockResult, newCameraProperties); - - verify(mockResult, times(1)) - .error("setDescriptionWhileRecordingFailed", "Device was not recording", null); - verify(mockResult, never()).success(any()); - } - @Test public void resumeVideoRecording_shouldSendVideoRecordingFailedErrorWhenVersionCodeSmallerThanN() { diff --git a/packages/camera/camera_android/example/integration_test/camera_test.dart b/packages/camera/camera_android/example/integration_test/camera_test.dart index 517a50d02cc5..e499872da5f3 100644 --- a/packages/camera/camera_android/example/integration_test/camera_test.dart +++ b/packages/camera/camera_android/example/integration_test/camera_test.dart @@ -205,51 +205,6 @@ void main() { expect(duration, lessThan(recordingTime - timePaused)); }); - testWidgets('Set description while recording', (WidgetTester tester) async { - final List cameras = - await CameraPlatform.instance.availableCameras(); - if (cameras.length < 2) { - return; - } - - final CameraController controller = CameraController( - cameras[0], - ResolutionPreset.low, - enableAudio: false, - ); - - await controller.initialize(); - await controller.prepareForVideoRecording(); - - await controller.startVideoRecording(); - sleep(const Duration(milliseconds: 500)); - await controller.setDescription(cameras[1]); - sleep(const Duration(milliseconds: 500)); - - expect(controller.description, cameras[1]); - }); - - testWidgets('Set description', (WidgetTester tester) async { - final List cameras = - await CameraPlatform.instance.availableCameras(); - if (cameras.length < 2) { - return; - } - - final CameraController controller = CameraController( - cameras[0], - ResolutionPreset.low, - enableAudio: false, - ); - - await controller.initialize(); - sleep(const Duration(milliseconds: 500)); - await controller.setDescription(cameras[1]); - sleep(const Duration(milliseconds: 500)); - - expect(controller.description, cameras[1]); - }); - testWidgets( 'image streaming', (WidgetTester tester) async { diff --git a/packages/camera/camera_android/example/lib/camera_controller.dart b/packages/camera/camera_android/example/lib/camera_controller.dart index fd4f09a027b9..8139dcdb0220 100644 --- a/packages/camera/camera_android/example/lib/camera_controller.dart +++ b/packages/camera/camera_android/example/lib/camera_controller.dart @@ -24,7 +24,6 @@ class CameraValue { required this.exposureMode, required this.focusMode, required this.deviceOrientation, - required this.description, this.lockedCaptureOrientation, this.recordingOrientation, this.isPreviewPaused = false, @@ -32,7 +31,7 @@ class CameraValue { }); /// Creates a new camera controller state for an uninitialized controller. - const CameraValue.uninitialized(CameraDescription description) + const CameraValue.uninitialized() : this( isInitialized: false, isRecordingVideo: false, @@ -44,7 +43,6 @@ class CameraValue { focusMode: FocusMode.auto, deviceOrientation: DeviceOrientation.portraitUp, isPreviewPaused: false, - description: description, ); /// True after [CameraController.initialize] has completed successfully. @@ -94,9 +92,6 @@ class CameraValue { /// The orientation of the currently running video recording. final DeviceOrientation? recordingOrientation; - /// The properties of the camera device controlled by this controller. - final CameraDescription description; - /// Creates a modified copy of the object. /// /// Explicitly specified fields get the specified value, all other fields get @@ -117,7 +112,6 @@ class CameraValue { Optional? lockedCaptureOrientation, Optional? recordingOrientation, bool? isPreviewPaused, - CameraDescription? description, Optional? previewPauseOrientation, }) { return CameraValue( @@ -138,7 +132,6 @@ class CameraValue { ? this.recordingOrientation : recordingOrientation.orNull, isPreviewPaused: isPreviewPaused ?? this.isPreviewPaused, - description: description ?? this.description, previewPauseOrientation: previewPauseOrientation == null ? this.previewPauseOrientation : previewPauseOrientation.orNull, @@ -172,14 +165,14 @@ class CameraValue { class CameraController extends ValueNotifier { /// Creates a new camera controller in an uninitialized state. CameraController( - CameraDescription cameraDescription, + this.description, this.resolutionPreset, { this.enableAudio = true, this.imageFormatGroup, - }) : super(CameraValue.uninitialized(cameraDescription)); + }) : super(const CameraValue.uninitialized()); /// The properties of the camera device controlled by this controller. - CameraDescription get description => value.description; + final CameraDescription description; /// The resolution this controller is targeting. /// @@ -209,9 +202,7 @@ class CameraController extends ValueNotifier { int get cameraId => _cameraId; /// Initializes the camera on the device. - Future initialize() => _initializeWithDescription(description); - - Future _initializeWithDescription(CameraDescription description) async { + Future initialize() async { final Completer initializeCompleter = Completer(); @@ -243,7 +234,6 @@ class CameraController extends ValueNotifier { value = value.copyWith( isInitialized: true, - description: description, previewSize: await initializeCompleter.future .then((CameraInitializedEvent event) => Size( event.previewWidth, @@ -284,16 +274,6 @@ class CameraController extends ValueNotifier { previewPauseOrientation: const Optional.absent()); } - /// Sets the description of the camera. - Future setDescription(CameraDescription description) async { - if (value.isRecordingVideo) { - await CameraPlatform.instance.setDescriptionWhileRecording(description); - value = value.copyWith(description: description); - } else { - await _initializeWithDescription(description); - } - } - /// Captures an image and returns the file where it was saved. /// /// Throws a [CameraException] if the capture fails. diff --git a/packages/camera/camera_android/example/lib/main.dart b/packages/camera/camera_android/example/lib/main.dart index 3731325a49fd..4d98aed9a4c2 100644 --- a/packages/camera/camera_android/example/lib/main.dart +++ b/packages/camera/camera_android/example/lib/main.dart @@ -123,7 +123,7 @@ class _CameraExampleHomeState extends State if (state == AppLifecycleState.inactive) { cameraController.dispose(); } else if (state == AppLifecycleState.resumed) { - _initializeCameraController(cameraController.description); + onNewCameraSelected(cameraController.description); } } @@ -603,7 +603,10 @@ class _CameraExampleHomeState extends State title: Icon(getCameraLensIcon(cameraDescription.lensDirection)), groupValue: controller?.description, value: cameraDescription, - onChanged: onChanged, + onChanged: + controller != null && controller!.value.isRecordingVideo + ? null + : onChanged, ), ), ); @@ -636,15 +639,17 @@ class _CameraExampleHomeState extends State } Future onNewCameraSelected(CameraDescription cameraDescription) async { - if (controller != null) { - return controller!.setDescription(cameraDescription); - } else { - return _initializeCameraController(cameraDescription); + final CameraController? oldController = controller; + if (oldController != null) { + // `controller` needs to be set to null before getting disposed, + // to avoid a race condition when we use the controller that is being + // disposed. This happens when camera permission dialog shows up, + // which triggers `didChangeAppLifecycleState`, which disposes and + // re-creates the controller. + controller = null; + await oldController.dispose(); } - } - Future _initializeCameraController( - CameraDescription cameraDescription) async { final CameraController cameraController = CameraController( cameraDescription, kIsWeb ? ResolutionPreset.max : ResolutionPreset.medium, diff --git a/packages/camera/camera_android/example/pubspec.yaml b/packages/camera/camera_android/example/pubspec.yaml index 08f94ced1f31..e23e31a886de 100644 --- a/packages/camera/camera_android/example/pubspec.yaml +++ b/packages/camera/camera_android/example/pubspec.yaml @@ -14,7 +14,7 @@ dependencies: # The example app is bundled with the plugin so we use a path dependency on # the parent directory to use the current plugin's version. path: ../ - camera_platform_interface: ^2.4.0 + camera_platform_interface: ^2.3.1 flutter: sdk: flutter path_provider: ^2.0.0 @@ -32,4 +32,3 @@ dev_dependencies: flutter: uses-material-design: true - diff --git a/packages/camera/camera_android/lib/src/android_camera.dart b/packages/camera/camera_android/lib/src/android_camera.dart index eca1003247c6..9ab9b578616a 100644 --- a/packages/camera/camera_android/lib/src/android_camera.dart +++ b/packages/camera/camera_android/lib/src/android_camera.dart @@ -505,17 +505,6 @@ class AndroidCamera extends CameraPlatform { ); } - @override - Future setDescriptionWhileRecording( - CameraDescription description) async { - await _channel.invokeMethod( - 'setDescriptionWhileRecording', - { - 'cameraName': description.name, - }, - ); - } - @override Widget buildPreview(int cameraId) { return Texture(textureId: cameraId); diff --git a/packages/camera/camera_android/pubspec.yaml b/packages/camera/camera_android/pubspec.yaml index 637658f4e691..fb3371912911 100644 --- a/packages/camera/camera_android/pubspec.yaml +++ b/packages/camera/camera_android/pubspec.yaml @@ -2,7 +2,7 @@ name: camera_android description: Android implementation of the camera plugin. repository: https://github.com/flutter/plugins/tree/main/packages/camera/camera_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22 -version: 0.10.5 +version: 0.10.4 environment: sdk: ">=2.14.0 <3.0.0" @@ -18,7 +18,7 @@ flutter: dartPluginClass: AndroidCamera dependencies: - camera_platform_interface: ^2.4.0 + camera_platform_interface: ^2.3.1 flutter: sdk: flutter flutter_plugin_android_lifecycle: ^2.0.2 diff --git a/packages/camera/camera_android/test/android_camera_test.dart b/packages/camera/camera_android/test/android_camera_test.dart index b56aa4e352aa..d80bd9cac7a3 100644 --- a/packages/camera/camera_android/test/android_camera_test.dart +++ b/packages/camera/camera_android/test/android_camera_test.dart @@ -700,29 +700,6 @@ void main() { ]); }); - test('Should set the description while recording', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: _channelName, - methods: {'setDescriptionWhileRecording': null}, - ); - const CameraDescription camera2Description = CameraDescription( - name: 'Test2', - lensDirection: CameraLensDirection.front, - sensorOrientation: 0); - - // Act - await camera.setDescriptionWhileRecording(camera2Description); - - // Assert - expect(channel.log, [ - isMethodCall('setDescriptionWhileRecording', - arguments: { - 'cameraName': camera2Description.name, - }), - ]); - }); - test('Should set the flash mode', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( diff --git a/packages/camera/camera_avfoundation/CHANGELOG.md b/packages/camera/camera_avfoundation/CHANGELOG.md index 169596fa647e..f0605b7914cc 100644 --- a/packages/camera/camera_avfoundation/CHANGELOG.md +++ b/packages/camera/camera_avfoundation/CHANGELOG.md @@ -1,7 +1,3 @@ -## 0.9.12 - -* Allows camera to be switched while video recording. - ## 0.9.11 * Adds back use of Optional type. diff --git a/packages/camera/camera_avfoundation/example/integration_test/camera_test.dart b/packages/camera/camera_avfoundation/example/integration_test/camera_test.dart index 5a6935a90114..34d460d44ec7 100644 --- a/packages/camera/camera_avfoundation/example/integration_test/camera_test.dart +++ b/packages/camera/camera_avfoundation/example/integration_test/camera_test.dart @@ -198,51 +198,6 @@ void main() { expect(duration, lessThan(recordingTime - timePaused)); }); - testWidgets('Set description while recording', (WidgetTester tester) async { - final List cameras = - await CameraPlatform.instance.availableCameras(); - if (cameras.length < 2) { - return; - } - - final CameraController controller = CameraController( - cameras[0], - ResolutionPreset.low, - enableAudio: false, - ); - - await controller.initialize(); - await controller.prepareForVideoRecording(); - - await controller.startVideoRecording(); - sleep(const Duration(milliseconds: 500)); - await controller.setDescription(cameras[1]); - sleep(const Duration(milliseconds: 500)); - - expect(controller.description, cameras[1]); - }); - - testWidgets('Set description', (WidgetTester tester) async { - final List cameras = - await CameraPlatform.instance.availableCameras(); - if (cameras.length < 2) { - return; - } - - final CameraController controller = CameraController( - cameras[0], - ResolutionPreset.low, - enableAudio: false, - ); - - await controller.initialize(); - sleep(const Duration(milliseconds: 500)); - await controller.setDescription(cameras[1]); - sleep(const Duration(milliseconds: 500)); - - expect(controller.description, cameras[1]); - }); - /// Start streaming with specifying the ImageFormatGroup. Future startStreaming(List cameras, ImageFormatGroup? imageFormatGroup) async { diff --git a/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj b/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj index c63d00860204..03c80d79c578 100644 --- a/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 50; + objectVersion = 46; objects = { /* Begin PBXBuildFile section */ diff --git a/packages/camera/camera_avfoundation/example/ios/Runner/Info.plist b/packages/camera/camera_avfoundation/example/ios/Runner/Info.plist index c50ce989f0c2..ff2e341a1803 100644 --- a/packages/camera/camera_avfoundation/example/ios/Runner/Info.plist +++ b/packages/camera/camera_avfoundation/example/ios/Runner/Info.plist @@ -52,7 +52,5 @@ UIViewControllerBasedStatusBarAppearance - CADisableMinimumFrameDurationOnPhone - diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m index b42aa34e2a17..0ae4887eb631 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m @@ -11,20 +11,15 @@ OCMStub([inputMock deviceInputWithDevice:[OCMArg any] error:[OCMArg setTo:nil]]) .andReturn(inputMock); - id videoSessionMock = OCMClassMock([AVCaptureSession class]); - OCMStub([videoSessionMock addInputWithNoConnections:[OCMArg any]]); // no-op - OCMStub([videoSessionMock canSetSessionPreset:[OCMArg any]]).andReturn(YES); - - id audioSessionMock = OCMClassMock([AVCaptureSession class]); - OCMStub([audioSessionMock addInputWithNoConnections:[OCMArg any]]); // no-op - OCMStub([audioSessionMock canSetSessionPreset:[OCMArg any]]).andReturn(YES); + id sessionMock = OCMClassMock([AVCaptureSession class]); + OCMStub([sessionMock addInputWithNoConnections:[OCMArg any]]); // no-op + OCMStub([sessionMock canSetSessionPreset:[OCMArg any]]).andReturn(YES); return [[FLTCam alloc] initWithCameraName:@"camera" resolutionPreset:@"medium" enableAudio:true orientation:UIDeviceOrientationPortrait - videoCaptureSession:videoSessionMock - audioCaptureSession:audioSessionMock + captureSession:sessionMock captureSessionQueue:captureSessionQueue error:nil]; } diff --git a/packages/camera/camera_avfoundation/example/lib/camera_controller.dart b/packages/camera/camera_avfoundation/example/lib/camera_controller.dart index 6e1804328d52..524186816aab 100644 --- a/packages/camera/camera_avfoundation/example/lib/camera_controller.dart +++ b/packages/camera/camera_avfoundation/example/lib/camera_controller.dart @@ -24,7 +24,6 @@ class CameraValue { required this.exposureMode, required this.focusMode, required this.deviceOrientation, - required this.description, this.lockedCaptureOrientation, this.recordingOrientation, this.isPreviewPaused = false, @@ -32,7 +31,7 @@ class CameraValue { }); /// Creates a new camera controller state for an uninitialized controller. - const CameraValue.uninitialized(CameraDescription description) + const CameraValue.uninitialized() : this( isInitialized: false, isRecordingVideo: false, @@ -44,7 +43,6 @@ class CameraValue { focusMode: FocusMode.auto, deviceOrientation: DeviceOrientation.portraitUp, isPreviewPaused: false, - description: description, ); /// True after [CameraController.initialize] has completed successfully. @@ -94,9 +92,6 @@ class CameraValue { /// The orientation of the currently running video recording. final DeviceOrientation? recordingOrientation; - /// The properties of the camera device controlled by this controller. - final CameraDescription description; - /// Creates a modified copy of the object. /// /// Explicitly specified fields get the specified value, all other fields get @@ -117,7 +112,6 @@ class CameraValue { Optional? lockedCaptureOrientation, Optional? recordingOrientation, bool? isPreviewPaused, - CameraDescription? description, Optional? previewPauseOrientation, }) { return CameraValue( @@ -138,7 +132,6 @@ class CameraValue { ? this.recordingOrientation : recordingOrientation.orNull, isPreviewPaused: isPreviewPaused ?? this.isPreviewPaused, - description: description ?? this.description, previewPauseOrientation: previewPauseOrientation == null ? this.previewPauseOrientation : previewPauseOrientation.orNull, @@ -172,14 +165,14 @@ class CameraValue { class CameraController extends ValueNotifier { /// Creates a new camera controller in an uninitialized state. CameraController( - CameraDescription cameraDescription, + this.description, this.resolutionPreset, { this.enableAudio = true, this.imageFormatGroup, - }) : super(CameraValue.uninitialized(cameraDescription)); + }) : super(const CameraValue.uninitialized()); /// The properties of the camera device controlled by this controller. - CameraDescription get description => value.description; + final CameraDescription description; /// The resolution this controller is targeting. /// @@ -209,9 +202,7 @@ class CameraController extends ValueNotifier { int get cameraId => _cameraId; /// Initializes the camera on the device. - Future initialize() => _initializeWithDescription(description); - - Future _initializeWithDescription(CameraDescription description) async { + Future initialize() async { final Completer initializeCompleter = Completer(); @@ -243,7 +234,6 @@ class CameraController extends ValueNotifier { value = value.copyWith( isInitialized: true, - description: description, previewSize: await initializeCompleter.future .then((CameraInitializedEvent event) => Size( event.previewWidth, @@ -284,16 +274,6 @@ class CameraController extends ValueNotifier { previewPauseOrientation: const Optional.absent()); } - /// Sets the description of the camera - Future setDescription(CameraDescription description) async { - if (value.isRecordingVideo) { - await CameraPlatform.instance.setDescriptionWhileRecording(description); - value = value.copyWith(description: description); - } else { - await _initializeWithDescription(description); - } - } - /// Captures an image and returns the file where it was saved. /// /// Throws a [CameraException] if the capture fails. diff --git a/packages/camera/camera_avfoundation/example/lib/main.dart b/packages/camera/camera_avfoundation/example/lib/main.dart index 3731325a49fd..4d98aed9a4c2 100644 --- a/packages/camera/camera_avfoundation/example/lib/main.dart +++ b/packages/camera/camera_avfoundation/example/lib/main.dart @@ -123,7 +123,7 @@ class _CameraExampleHomeState extends State if (state == AppLifecycleState.inactive) { cameraController.dispose(); } else if (state == AppLifecycleState.resumed) { - _initializeCameraController(cameraController.description); + onNewCameraSelected(cameraController.description); } } @@ -603,7 +603,10 @@ class _CameraExampleHomeState extends State title: Icon(getCameraLensIcon(cameraDescription.lensDirection)), groupValue: controller?.description, value: cameraDescription, - onChanged: onChanged, + onChanged: + controller != null && controller!.value.isRecordingVideo + ? null + : onChanged, ), ), ); @@ -636,15 +639,17 @@ class _CameraExampleHomeState extends State } Future onNewCameraSelected(CameraDescription cameraDescription) async { - if (controller != null) { - return controller!.setDescription(cameraDescription); - } else { - return _initializeCameraController(cameraDescription); + final CameraController? oldController = controller; + if (oldController != null) { + // `controller` needs to be set to null before getting disposed, + // to avoid a race condition when we use the controller that is being + // disposed. This happens when camera permission dialog shows up, + // which triggers `didChangeAppLifecycleState`, which disposes and + // re-creates the controller. + controller = null; + await oldController.dispose(); } - } - Future _initializeCameraController( - CameraDescription cameraDescription) async { final CameraController cameraController = CameraController( cameraDescription, kIsWeb ? ResolutionPreset.max : ResolutionPreset.medium, diff --git a/packages/camera/camera_avfoundation/example/pubspec.yaml b/packages/camera/camera_avfoundation/example/pubspec.yaml index 872a22021d2e..7c85ba807193 100644 --- a/packages/camera/camera_avfoundation/example/pubspec.yaml +++ b/packages/camera/camera_avfoundation/example/pubspec.yaml @@ -14,7 +14,7 @@ dependencies: # The example app is bundled with the plugin so we use a path dependency on # the parent directory to use the current plugin's version. path: ../ - camera_platform_interface: ^2.4.0 + camera_platform_interface: ^2.2.0 flutter: sdk: flutter path_provider: ^2.0.0 diff --git a/packages/camera/camera_avfoundation/ios/Classes/CameraPlugin.m b/packages/camera/camera_avfoundation/ios/Classes/CameraPlugin.m index f9b2a911b67d..b85f68d1f957 100644 --- a/packages/camera/camera_avfoundation/ios/Classes/CameraPlugin.m +++ b/packages/camera/camera_avfoundation/ios/Classes/CameraPlugin.m @@ -261,8 +261,6 @@ - (void)handleMethodCallAsync:(FlutterMethodCall *)call [_camera pausePreviewWithResult:result]; } else if ([@"resumePreview" isEqualToString:call.method]) { [_camera resumePreviewWithResult:result]; - } else if ([@"setDescriptionWhileRecording" isEqualToString:call.method]) { - [_camera setDescriptionWhileRecording:(call.arguments[@"cameraName"]) result:result]; } else { [result sendNotImplemented]; } diff --git a/packages/camera/camera_avfoundation/ios/Classes/FLTCam.h b/packages/camera/camera_avfoundation/ios/Classes/FLTCam.h index df2a155855dd..85b8e2ae06f2 100644 --- a/packages/camera/camera_avfoundation/ios/Classes/FLTCam.h +++ b/packages/camera/camera_avfoundation/ios/Classes/FLTCam.h @@ -95,8 +95,6 @@ NS_ASSUME_NONNULL_BEGIN - (void)applyFocusMode:(FLTFocusMode)focusMode onDevice:(AVCaptureDevice *)captureDevice; - (void)pausePreviewWithResult:(FLTThreadSafeFlutterResult *)result; - (void)resumePreviewWithResult:(FLTThreadSafeFlutterResult *)result; -- (void)setDescriptionWhileRecording:(NSString *)cameraName - result:(FLTThreadSafeFlutterResult *)result; - (void)setExposurePointWithResult:(FLTThreadSafeFlutterResult *)result x:(double)x y:(double)y; - (void)setFocusPointWithResult:(FLTThreadSafeFlutterResult *)result x:(double)x y:(double)y; - (void)setExposureOffsetWithResult:(FLTThreadSafeFlutterResult *)result offset:(double)offset; diff --git a/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m b/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m index d5247e00382e..a7d6cd24be3c 100644 --- a/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m +++ b/packages/camera/camera_avfoundation/ios/Classes/FLTCam.m @@ -43,8 +43,7 @@ @interface FLTCam () setDescriptionWhileRecording( - CameraDescription description) async { - await _channel.invokeMethod( - 'setDescriptionWhileRecording', - { - 'cameraName': description.name, - }, - ); - } - @override Widget buildPreview(int cameraId) { return Texture(textureId: cameraId); diff --git a/packages/camera/camera_avfoundation/pubspec.yaml b/packages/camera/camera_avfoundation/pubspec.yaml index 78c9156a7b79..b272a4c5c68d 100644 --- a/packages/camera/camera_avfoundation/pubspec.yaml +++ b/packages/camera/camera_avfoundation/pubspec.yaml @@ -2,7 +2,7 @@ name: camera_avfoundation description: iOS implementation of the camera plugin. repository: https://github.com/flutter/plugins/tree/main/packages/camera/camera_avfoundation issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22 -version: 0.9.12 +version: 0.9.11 environment: sdk: ">=2.14.0 <3.0.0" @@ -17,7 +17,7 @@ flutter: dartPluginClass: AVFoundationCamera dependencies: - camera_platform_interface: ^2.4.0 + camera_platform_interface: ^2.3.1 flutter: sdk: flutter stream_transform: ^2.0.0 diff --git a/packages/camera/camera_avfoundation/test/avfoundation_camera_test.dart b/packages/camera/camera_avfoundation/test/avfoundation_camera_test.dart index e756f38ff122..5d0b74cf0c0c 100644 --- a/packages/camera/camera_avfoundation/test/avfoundation_camera_test.dart +++ b/packages/camera/camera_avfoundation/test/avfoundation_camera_test.dart @@ -701,29 +701,6 @@ void main() { ]); }); - test('Should set the description while recording', () async { - // Arrange - final MethodChannelMock channel = MethodChannelMock( - channelName: _channelName, - methods: {'setDescriptionWhileRecording': null}, - ); - const CameraDescription camera2Description = CameraDescription( - name: 'Test2', - lensDirection: CameraLensDirection.front, - sensorOrientation: 0); - - // Act - await camera.setDescriptionWhileRecording(camera2Description); - - // Assert - expect(channel.log, [ - isMethodCall('setDescriptionWhileRecording', - arguments: { - 'cameraName': camera2Description.name, - }), - ]); - }); - test('Should set the flash mode', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( From d2fba3822fb53f546ec46744b7ef26bcb724dfdc Mon Sep 17 00:00:00 2001 From: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> Date: Tue, 14 Feb 2023 13:50:52 -0500 Subject: [PATCH 100/130] [webview_flutter_android][webview_flutter_wkwebview] Adds support to retrieve native `WebView` (#7071) * implementation of the webViewIdentifier field * change to external classes * formatting * update readmes * iOS * improve * hmmmm * add note about not using other apis * project changes * add external api tests to project * ordering * fix docs and use id --- .../webview_flutter_android/CHANGELOG.md | 4 ++ .../webview_flutter_android/README.md | 16 ++++++ .../WebViewFlutterAndroidExternalApi.java | 50 +++++++++++++++++++ .../webviewflutter/WebViewFlutterPlugin.java | 7 ++- ...WebViewFlutterAndroidExternalApiTest.java} | 23 +++++++-- .../lib/src/android_webview_controller.dart | 10 ++++ .../webview_flutter_android/pubspec.yaml | 2 +- .../test/android_webview_controller_test.dart | 24 +++++++++ .../webview_flutter_wkwebview/CHANGELOG.md | 4 ++ .../webview_flutter_wkwebview/README.md | 18 +++++++ .../ios/Runner.xcodeproj/project.pbxproj | 43 +++++++++++++--- ...FWebViewFlutterWKWebViewExternalAPITests.m | 28 +++++++++++ .../ios/Classes/FLTWebViewFlutterPlugin.h | 5 ++ .../FWFWebViewFlutterWKWebViewExternalAPI.h | 37 ++++++++++++++ .../FWFWebViewFlutterWKWebViewExternalAPI.m | 21 ++++++++ .../ios/Classes/webview-umbrella.h | 1 + .../lib/src/webkit_webview_controller.dart | 10 ++++ .../webview_flutter_wkwebview/pubspec.yaml | 2 +- .../test/webkit_webview_controller_test.dart | 20 ++++++++ 19 files changed, 311 insertions(+), 14 deletions(-) create mode 100644 packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterAndroidExternalApi.java rename packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/{WebViewFlutterPluginTest.java => WebViewFlutterAndroidExternalApiTest.java} (58%) create mode 100644 packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFWebViewFlutterWKWebViewExternalAPITests.m create mode 100644 packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFWebViewFlutterWKWebViewExternalAPI.h create mode 100644 packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFWebViewFlutterWKWebViewExternalAPI.m diff --git a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md index 136d71485a0f..ed6c546ed147 100644 --- a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## 3.3.0 + +* Adds support to access native `WebView`. + ## 3.2.4 * Renames Pigeon output files. diff --git a/packages/webview_flutter/webview_flutter_android/README.md b/packages/webview_flutter/webview_flutter_android/README.md index 1a54808379fb..d2f4d94bfed4 100644 --- a/packages/webview_flutter/webview_flutter_android/README.md +++ b/packages/webview_flutter/webview_flutter_android/README.md @@ -32,6 +32,22 @@ This can be configured for versions >=23 with `AndroidWebViewWidgetCreationParams.displayWithHybridComposition`. See https://pub.dev/packages/webview_flutter#platform-specific-features for more details on setting platform-specific features in the main plugin. +### External Native API + +The plugin also provides a native API accessible by the native code of Android applications or +packages. This API follows the convention of breaking changes of the Dart API, which means that any +changes to the class that are not backwards compatible will only be made with a major version change +of the plugin. Native code other than this external API does not follow breaking change conventions, +so app or plugin clients should not use any other native APIs. + +The API can be accessed by importing the native class `WebViewFlutterAndroidExternalApi`: + +Java: + +```java +import io.flutter.plugins.webviewflutter.WebViewFlutterAndroidExternalApi; +``` + ## Contributing This package uses [pigeon][3] to generate the communication layer between Flutter and the host diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterAndroidExternalApi.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterAndroidExternalApi.java new file mode 100644 index 000000000000..3819d7b26f62 --- /dev/null +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterAndroidExternalApi.java @@ -0,0 +1,50 @@ +// Copyright 2013 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.webviewflutter; + +import android.webkit.WebView; +import androidx.annotation.Nullable; +import io.flutter.embedding.engine.FlutterEngine; + +/** + * App and package facing native API provided by the `webview_flutter_android` plugin. + * + *

This class follows the convention of breaking changes of the Dart API, which means that any + * changes to the class that are not backwards compatible will only be made with a major version + * change of the plugin. + * + *

Native code other than this external API does not follow breaking change conventions, so app + * or plugin clients should not use any other native APIs. + */ +@SuppressWarnings("unused") +public interface WebViewFlutterAndroidExternalApi { + /** + * Retrieves the {@link WebView} that is associated with `identifier`. + * + *

See the Dart method `AndroidWebViewController.webViewIdentifier` to get the identifier of an + * underlying `WebView`. + * + * @param engine the execution environment the {@link WebViewFlutterPlugin} should belong to. If + * the engine doesn't contain an attached instance of {@link WebViewFlutterPlugin}, this + * method returns null. + * @param identifier the associated identifier of the `WebView`. + * @return the `WebView` associated with `identifier` or null if a `WebView` instance associated + * with `identifier` could not be found. + */ + @Nullable + static WebView getWebView(FlutterEngine engine, long identifier) { + final WebViewFlutterPlugin webViewPlugin = + (WebViewFlutterPlugin) engine.getPlugins().get(WebViewFlutterPlugin.class); + + if (webViewPlugin != null && webViewPlugin.getInstanceManager() != null) { + final Object instance = webViewPlugin.getInstanceManager().getInstance(identifier); + if (instance instanceof WebView) { + return (WebView) instance; + } + } + + return null; + } +} diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java index 1c5a55057ca6..04a9735e0281 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java @@ -33,7 +33,7 @@ *

Call {@link #registerWith} to use the stable {@code io.flutter.plugin.common} package instead. */ public class WebViewFlutterPlugin implements FlutterPlugin, ActivityAware { - private InstanceManager instanceManager; + @Nullable private InstanceManager instanceManager; private FlutterPluginBinding pluginBinding; private WebViewHostApiImpl webViewHostApi; @@ -148,7 +148,10 @@ public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) { @Override public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { - instanceManager.close(); + if (instanceManager != null) { + instanceManager.close(); + instanceManager = null; + } } @Override diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewFlutterPluginTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewFlutterAndroidExternalApiTest.java similarity index 58% rename from packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewFlutterPluginTest.java rename to packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewFlutterAndroidExternalApiTest.java index 16dc6cf5de2b..0877dcaf2b06 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewFlutterPluginTest.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewFlutterAndroidExternalApiTest.java @@ -4,11 +4,16 @@ package io.flutter.plugins.webviewflutter; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import android.content.Context; +import android.webkit.WebView; +import io.flutter.embedding.engine.FlutterEngine; import io.flutter.embedding.engine.plugins.FlutterPlugin; +import io.flutter.embedding.engine.plugins.PluginRegistry; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.platform.PlatformViewRegistry; import org.junit.Rule; @@ -17,7 +22,7 @@ import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; -public class WebViewFlutterPluginTest { +public class WebViewFlutterAndroidExternalApiTest { @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); @Mock Context mockContext; @@ -29,7 +34,7 @@ public class WebViewFlutterPluginTest { @Mock FlutterPlugin.FlutterPluginBinding mockPluginBinding; @Test - public void getInstanceManagerAfterOnAttachedToEngine() { + public void getWebView() { final WebViewFlutterPlugin webViewFlutterPlugin = new WebViewFlutterPlugin(); when(mockPluginBinding.getApplicationContext()).thenReturn(mockContext); @@ -38,7 +43,19 @@ public void getInstanceManagerAfterOnAttachedToEngine() { webViewFlutterPlugin.onAttachedToEngine(mockPluginBinding); - assertNotNull(webViewFlutterPlugin.getInstanceManager()); + final InstanceManager instanceManager = webViewFlutterPlugin.getInstanceManager(); + assertNotNull(instanceManager); + + final WebView mockWebView = mock(WebView.class); + instanceManager.addDartCreatedInstance(mockWebView, 0); + + final PluginRegistry mockPluginRegistry = mock(PluginRegistry.class); + when(mockPluginRegistry.get(WebViewFlutterPlugin.class)).thenReturn(webViewFlutterPlugin); + + final FlutterEngine mockFlutterEngine = mock(FlutterEngine.class); + when(mockFlutterEngine.getPlugins()).thenReturn(mockPluginRegistry); + + assertEquals(WebViewFlutterAndroidExternalApi.getWebView(mockFlutterEngine, 0), mockWebView); webViewFlutterPlugin.onDetachedFromEngine(mockPluginBinding); } diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart index fd287a515c65..6bd3dc03746c 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart @@ -144,6 +144,16 @@ class AndroidWebViewController extends PlatformWebViewController { return webViewProxy.setWebContentsDebuggingEnabled(enabled); } + /// Identifier used to retrieve the underlying native `WKWebView`. + /// + /// This is typically used by other plugins to retrieve the native `WebView` + /// from an `InstanceManager`. + /// + /// See Java method `WebViewFlutterPlugin.getWebView`. + int get webViewIdentifier => + // ignore: invalid_use_of_visible_for_testing_member + android_webview.WebView.api.instanceManager.getIdentifier(_webView)!; + @override Future loadFile( String absoluteFilePath, diff --git a/packages/webview_flutter/webview_flutter_android/pubspec.yaml b/packages/webview_flutter/webview_flutter_android/pubspec.yaml index d90844d9ce08..ac8971006ba2 100644 --- a/packages/webview_flutter/webview_flutter_android/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_android/pubspec.yaml @@ -2,7 +2,7 @@ name: webview_flutter_android description: A Flutter plugin that provides a WebView widget on Android. repository: https://github.com/flutter/plugins/tree/main/packages/webview_flutter/webview_flutter_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22 -version: 3.2.4 +version: 3.3.0 environment: sdk: ">=2.17.0 <3.0.0" diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.dart index 03e71ec5d987..43bab384e0cc 100644 --- a/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.dart +++ b/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.dart @@ -14,6 +14,7 @@ import 'package:mockito/mockito.dart'; import 'package:webview_flutter_android/src/android_proxy.dart'; import 'package:webview_flutter_android/src/android_webview.dart' as android_webview; +import 'package:webview_flutter_android/src/android_webview_api_impls.dart'; import 'package:webview_flutter_android/src/instance_manager.dart'; import 'package:webview_flutter_android/src/platform_views_service_proxy.dart'; import 'package:webview_flutter_android/webview_flutter_android.dart'; @@ -884,6 +885,29 @@ void main() { verify(mockSettings.setMediaPlaybackRequiresUserGesture(true)).called(1); }); + test('webViewIdentifier', () { + final MockWebView mockWebView = MockWebView(); + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: (_) {}, + ); + instanceManager.addHostCreatedInstance(mockWebView, 0); + + android_webview.WebView.api = WebViewHostApiImpl( + instanceManager: instanceManager, + ); + + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + ); + + expect( + controller.webViewIdentifier, + 0, + ); + + android_webview.WebView.api = WebViewHostApiImpl(); + }); + group('AndroidWebViewWidget', () { testWidgets('Builds Android view using supplied parameters', (WidgetTester tester) async { diff --git a/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md b/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md index d8442c2c1f0e..d0c5a726b5f7 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md @@ -1,3 +1,7 @@ +## 3.1.0 + +* Adds support to access native `WKWebView`. + ## 3.0.5 * Renames Pigeon output files. diff --git a/packages/webview_flutter/webview_flutter_wkwebview/README.md b/packages/webview_flutter/webview_flutter_wkwebview/README.md index 79359636e742..a393a71d2248 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/README.md +++ b/packages/webview_flutter/webview_flutter_wkwebview/README.md @@ -7,6 +7,24 @@ The Apple WKWebView implementation of [`webview_flutter`][1]. This package is [endorsed][2], which means you can simply use `webview_flutter` normally. This package will be automatically included in your app when you do. +### External Native API + +The plugin also provides a native API accessible by the native code of iOS applications or packages. +This API follows the convention of breaking changes of the Dart API, which means that any changes to +the class that are not backwards compatible will only be made with a major version change of the +plugin. Native code other than this external API does not follow breaking change conventions, so +app or plugin clients should not use any other native APIs. + +The API can be accessed by importing the native plugin `webview_flutter_wkwebview`: + +Objective-C: + +```objectivec +@import webview_flutter_wkwebview; +``` + +Then you will have access to the native class `FWFWebViewFlutterWKWebViewExternalAPI`. + ## Contributing This package uses [pigeon][3] to generate the communication layer between Flutter and the host diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/Runner.xcodeproj/project.pbxproj b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/Runner.xcodeproj/project.pbxproj index 1efee8f844ef..9e1038d08279 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,12 +3,13 @@ archiveVersion = 1; classes = { }; - objectVersion = 50; + objectVersion = 54; objects = { /* 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 */; }; + 8F4FF949299ADC2D000A6586 /* FWFWebViewFlutterWKWebViewExternalAPITests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8F4FF948299ADC2D000A6586 /* FWFWebViewFlutterWKWebViewExternalAPITests.m */; }; 8FA6A87928062CD000A4B183 /* FWFInstanceManagerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8FA6A87828062CD000A4B183 /* FWFInstanceManagerTests.m */; }; 8FB79B5328134C3100C101D3 /* FWFWebViewHostApiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8FB79B5228134C3100C101D3 /* FWFWebViewHostApiTests.m */; }; 8FB79B55281B24F600C101D3 /* FWFDataConvertersTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8FB79B54281B24F600C101D3 /* FWFDataConvertersTests.m */; }; @@ -76,6 +77,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 = ""; }; + 8F4FF948299ADC2D000A6586 /* FWFWebViewFlutterWKWebViewExternalAPITests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FWFWebViewFlutterWKWebViewExternalAPITests.m; sourceTree = ""; }; 8FA6A87828062CD000A4B183 /* FWFInstanceManagerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FWFInstanceManagerTests.m; sourceTree = ""; }; 8FB79B5228134C3100C101D3 /* FWFWebViewHostApiTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FWFWebViewHostApiTests.m; sourceTree = ""; }; 8FB79B54281B24F600C101D3 /* FWFDataConvertersTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FWFDataConvertersTests.m; sourceTree = ""; }; @@ -145,6 +147,7 @@ isa = PBXGroup; children = ( 68BDCAED23C3F7CB00D9C032 /* Info.plist */, + 8F4FF948299ADC2D000A6586 /* FWFWebViewFlutterWKWebViewExternalAPITests.m */, 8FA6A87828062CD000A4B183 /* FWFInstanceManagerTests.m */, 8FB79B5228134C3100C101D3 /* FWFWebViewHostApiTests.m */, 8FB79B54281B24F600C101D3 /* FWFDataConvertersTests.m */, @@ -379,10 +382,12 @@ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( @@ -415,6 +420,7 @@ }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -463,6 +469,7 @@ 8FB79B5328134C3100C101D3 /* FWFWebViewHostApiTests.m in Sources */, 8FB79B73282096B500C101D3 /* FWFScriptMessageHandlerHostApiTests.m in Sources */, 8FB79B7928209D1300C101D3 /* FWFUserContentControllerHostApiTests.m in Sources */, + 8F4FF949299ADC2D000A6586 /* FWFWebViewFlutterWKWebViewExternalAPITests.m in Sources */, 8FB79B6B28204EE500C101D3 /* FWFWebsiteDataStoreHostApiTests.m in Sources */, 8FB79B8F2820BAB300C101D3 /* FWFScrollViewHostApiTests.m in Sources */, 8FB79B912820BAC700C101D3 /* FWFUIViewHostApiTests.m in Sources */, @@ -533,7 +540,11 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; INFOPLIST_FILE = RunnerTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner"; @@ -547,7 +558,11 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; INFOPLIST_FILE = RunnerTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner"; @@ -672,7 +687,10 @@ "$(PROJECT_DIR)/Flutter", ); INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", @@ -695,7 +713,10 @@ "$(PROJECT_DIR)/Flutter", ); INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", @@ -711,7 +732,11 @@ buildSettings = { CODE_SIGN_STYLE = Automatic; INFOPLIST_FILE = RunnerUITests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerUITests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -724,7 +749,11 @@ buildSettings = { CODE_SIGN_STYLE = Automatic; INFOPLIST_FILE = RunnerUITests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerUITests; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFWebViewFlutterWKWebViewExternalAPITests.m b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFWebViewFlutterWKWebViewExternalAPITests.m new file mode 100644 index 000000000000..1452edeaa647 --- /dev/null +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFWebViewFlutterWKWebViewExternalAPITests.m @@ -0,0 +1,28 @@ +// Copyright 2013 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. + +#import +#import + +@import webview_flutter_wkwebview; + +@interface FWFWebViewFlutterWKWebViewExternalAPITests : XCTestCase +@end + +@implementation FWFWebViewFlutterWKWebViewExternalAPITests +- (void)testWebViewForIdentifier { + WKWebView *webView = [[WKWebView alloc] init]; + FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; + [instanceManager addDartCreatedInstance:webView withIdentifier:0]; + + id mockPluginRegistry = OCMProtocolMock(@protocol(FlutterPluginRegistry)); + OCMStub([mockPluginRegistry valuePublishedByPlugin:@"FLTWebViewFlutterPlugin"]) + .andReturn(instanceManager); + + XCTAssertEqualObjects( + [FWFWebViewFlutterWKWebViewExternalAPI webViewForIdentifier:0 + withPluginRegistry:mockPluginRegistry], + webView); +} +@end diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTWebViewFlutterPlugin.h b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTWebViewFlutterPlugin.h index 2a80c7d886f2..a1c035e40185 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTWebViewFlutterPlugin.h +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTWebViewFlutterPlugin.h @@ -3,6 +3,11 @@ // found in the LICENSE file. #import +#import + +NS_ASSUME_NONNULL_BEGIN @interface FLTWebViewFlutterPlugin : NSObject @end + +NS_ASSUME_NONNULL_END diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFWebViewFlutterWKWebViewExternalAPI.h b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFWebViewFlutterWKWebViewExternalAPI.h new file mode 100644 index 000000000000..297f8c37ec3e --- /dev/null +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFWebViewFlutterWKWebViewExternalAPI.h @@ -0,0 +1,37 @@ +// Copyright 2013 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. + +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * App and package facing native API provided by the `webview_flutter_wkwebview` plugin. + * + * This class follows the convention of breaking changes of the Dart API, which means that any + * changes to the class that are not backwards compatible will only be made with a major version + * change of the plugin. Native code other than this external API does not follow breaking change + * conventions, so app or plugin clients should not use any other native APIs. + */ +@interface FWFWebViewFlutterWKWebViewExternalAPI : NSObject +/** + * Retrieves the `WKWebView` that is associated with `identifier`. + * + * See the Dart method `WebKitWebViewController.webViewIdentifier` to get the identifier of an + * underlying `WKWebView`. + * + * @param identifier The associated identifier of the `WebView`. + * @param registry The plugin registry the `FLTWebViewFlutterPlugin` should belong to. If + * the registry doesn't contain an attached instance of `FLTWebViewFlutterPlugin`, + * this method returns nil. + * @return The `WKWebView` associated with `identifier` or nil if a `WKWebView` instance associated + * with `identifier` could not be found. + */ ++ (nullable WKWebView *)webViewForIdentifier:(long)identifier + withPluginRegistry:(id)registry; +@end + +NS_ASSUME_NONNULL_END diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFWebViewFlutterWKWebViewExternalAPI.m b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFWebViewFlutterWKWebViewExternalAPI.m new file mode 100644 index 000000000000..4e5d6efeb129 --- /dev/null +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFWebViewFlutterWKWebViewExternalAPI.m @@ -0,0 +1,21 @@ +// Copyright 2013 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. + +#import "FWFWebViewFlutterWKWebViewExternalAPI.h" +#import "FWFInstanceManager.h" + +@implementation FWFWebViewFlutterWKWebViewExternalAPI ++ (nullable WKWebView *)webViewForIdentifier:(long)identifier + withPluginRegistry:(id)registry { + FWFInstanceManager *instanceManager = + (FWFInstanceManager *)[registry valuePublishedByPlugin:@"FLTWebViewFlutterPlugin"]; + + id instance = [instanceManager instanceForIdentifier:identifier]; + if ([instance isKindOfClass:[WKWebView class]]) { + return instance; + } + + return nil; +} +@end diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/webview-umbrella.h b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/webview-umbrella.h index dbcd876d15c9..b9ba942b4ed5 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/webview-umbrella.h +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/webview-umbrella.h @@ -17,5 +17,6 @@ #import #import #import +#import #import #import diff --git a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webkit_webview_controller.dart b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webkit_webview_controller.dart index 02b5b73b5971..8abd0c1afe8a 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webkit_webview_controller.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webkit_webview_controller.dart @@ -164,6 +164,16 @@ class WebKitWebViewController extends PlatformWebViewController { WebKitWebViewControllerCreationParams get _webKitParams => params as WebKitWebViewControllerCreationParams; + /// Identifier used to retrieve the underlying native `WKWebView`. + /// + /// This is typically used by other plugins to retrieve the native `WKWebView` + /// from an `FWFInstanceManager`. + /// + /// See Objective-C method + /// `FLTWebViewFlutterPlugin:webViewForIdentifier:withPluginRegistry`. + int get webViewIdentifier => + _webKitParams._instanceManager.getIdentifier(_webView)!; + @override Future loadFile(String absoluteFilePath) { return _webView.loadFileUrl( diff --git a/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml b/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml index 5c4df9922840..d1aaa7cf9203 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml @@ -2,7 +2,7 @@ name: webview_flutter_wkwebview description: A Flutter plugin that provides a WebView widget based on Apple's WKWebView control. repository: https://github.com/flutter/plugins/tree/main/packages/webview_flutter/webview_flutter_wkwebview issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22 -version: 3.0.5 +version: 3.1.0 environment: sdk: ">=2.17.0 <3.0.0" diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_controller_test.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_controller_test.dart index 0360c13b052a..b7b729a97926 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_controller_test.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_controller_test.dart @@ -81,6 +81,7 @@ void main() { return nonNullMockWebView; }, ), + instanceManager: instanceManager, ); final WebKitWebViewController controller = WebKitWebViewController( @@ -935,6 +936,25 @@ void main() { expect(callbackProgress, 0); }); + + test('webViewIdentifier', () { + final InstanceManager instanceManager = InstanceManager( + onWeakReferenceRemoved: (_) {}, + ); + final MockWKWebView mockWebView = MockWKWebView(); + when(mockWebView.copy()).thenReturn(MockWKWebView()); + instanceManager.addHostCreatedInstance(mockWebView, 0); + + final WebKitWebViewController controller = createControllerWithMocks( + createMockWebView: (_, {dynamic observeValue}) => mockWebView, + instanceManager: instanceManager, + ); + + expect( + controller.webViewIdentifier, + instanceManager.getIdentifier(mockWebView), + ); + }); }); group('WebKitJavaScriptChannelParams', () { From 7a7e43e79233f83ad7102843d1b5f3275b36fb54 Mon Sep 17 00:00:00 2001 From: Balvinder Singh Gambhir Date: Wed, 15 Feb 2023 00:21:56 +0530 Subject: [PATCH 101/130] [google_maps_flutter_android] Fixes initial padding not working while map has not been created yet. (#7135) * fix initial padding not working * fix changelog * remove unused imports * removed visiblefortesting from google map * add private back * applied patch * replace 10 with padding * add line * remove line --- .../google_maps_flutter_android/CHANGELOG.md | 6 ++++- .../googlemaps/GoogleMapController.java | 25 ++++++++++++++++++- .../googlemaps/GoogleMapControllerTest.java | 21 ++++++++++++++++ .../google_maps_flutter_android/pubspec.yaml | 2 +- 4 files changed, 51 insertions(+), 3 deletions(-) diff --git a/packages/google_maps_flutter/google_maps_flutter_android/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter_android/CHANGELOG.md index 35dd55033b78..68b9f677e2db 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/CHANGELOG.md +++ b/packages/google_maps_flutter/google_maps_flutter_android/CHANGELOG.md @@ -1,6 +1,10 @@ +## 2.4.5 + +* Fixes Initial padding not working when map has not been created yet. + ## 2.4.4 -* Fixes Points losing precision when converting to LatLng +* Fixes Points losing precision when converting to LatLng. * Updates minimum Flutter version to 3.0. ## 2.4.3 diff --git a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java index 66d3e283b8df..a57cd1a34c97 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java +++ b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java @@ -70,7 +70,7 @@ final class GoogleMapController private boolean trafficEnabled = false; private boolean buildingsEnabled = true; private boolean disposed = false; - private final float density; + @VisibleForTesting final float density; private MethodChannel.Result mapReadyResult; private final Context context; private final LifecycleProvider lifecycleProvider; @@ -84,6 +84,7 @@ final class GoogleMapController private List initialPolylines; private List initialCircles; private List> initialTileOverlays; + @VisibleForTesting List initialPadding; GoogleMapController( int id, @@ -209,6 +210,13 @@ public void onMapReady(GoogleMap googleMap) { updateInitialPolylines(); updateInitialCircles(); updateInitialTileOverlays(); + if (initialPadding != null && initialPadding.size() == 4) { + setPadding( + initialPadding.get(0), + initialPadding.get(1), + initialPadding.get(2), + initialPadding.get(3)); + } } @Override @@ -741,7 +749,22 @@ public void setPadding(float top, float left, float bottom, float right) { (int) (top * density), (int) (right * density), (int) (bottom * density)); + } else { + setInitialPadding(top, left, bottom, right); + } + } + + @VisibleForTesting + void setInitialPadding(float top, float left, float bottom, float right) { + if (initialPadding == null) { + initialPadding = new ArrayList<>(); + } else { + initialPadding.clear(); } + initialPadding.add(top); + initialPadding.add(left); + initialPadding.add(bottom); + initialPadding.add(right); } @Override diff --git a/packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/GoogleMapControllerTest.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/GoogleMapControllerTest.java index d8082b57e3db..52576962ba8d 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/GoogleMapControllerTest.java +++ b/packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/GoogleMapControllerTest.java @@ -4,10 +4,12 @@ package io.flutter.plugins.googlemaps; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import android.content.Context; @@ -20,6 +22,7 @@ import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; import java.util.HashMap; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -145,4 +148,22 @@ public void MethodCalledAfterControllerIsDestroyed() throws InterruptedException argument.getValue().onMapLoaded(); verify(mapView, never()).invalidate(); } + + @Test + public void OnMapReadySetsPaddingIfInitialPaddingIsThere() { + float padding = 10f; + int paddingWithDensity = (int) (padding * googleMapController.density); + googleMapController.setInitialPadding(padding, padding, padding, padding); + googleMapController.onMapReady(mockGoogleMap); + verify(mockGoogleMap, times(1)) + .setPadding(paddingWithDensity, paddingWithDensity, paddingWithDensity, paddingWithDensity); + } + + @Test + public void SetPaddingStoresThePaddingValuesInInInitialPaddingWhenGoogleMapIsNull() { + assertNull(googleMapController.initialPadding); + googleMapController.setPadding(0f, 0f, 0f, 0f); + assertNotNull(googleMapController.initialPadding); + Assert.assertEquals(4, googleMapController.initialPadding.size()); + } } diff --git a/packages/google_maps_flutter/google_maps_flutter_android/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_android/pubspec.yaml index 0e2842bf24f2..cf8bc81e7e7c 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter_android/pubspec.yaml @@ -2,7 +2,7 @@ name: google_maps_flutter_android description: Android implementation of the google_maps_flutter plugin. repository: https://github.com/flutter/plugins/tree/main/packages/google_maps_flutter/google_maps_flutter_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+maps%22 -version: 2.4.4 +version: 2.4.5 environment: sdk: ">=2.14.0 <3.0.0" From 677b43ad9ab99986b8b3bb3b6f85e492fd5b8d2e Mon Sep 17 00:00:00 2001 From: stuartmorgan Date: Tue, 14 Feb 2023 11:15:35 -0800 Subject: [PATCH 102/130] [ci] Remove repo tooling (#7172) * Remove tooling * Remove CI testing of tooling * Switch invocations to published version * Update tool_runner.sh * Update Cirrus * Fix global run command * Update stale comment * Re-add a minimal stub * Roll forward --- .ci.yaml | 9 - .ci/scripts/build_examples_win32.sh | 2 +- .ci/scripts/create_all_plugins_app.sh | 2 +- .ci/scripts/drive_examples_win32.sh | 2 +- .ci/scripts/native_test_win32.sh | 2 +- .ci/scripts/plugin_tools_tests.sh | 7 - .ci/scripts/prepare_tool.sh | 5 +- .ci/targets/plugin_tools_tests.yaml | 5 - .cirrus.yml | 9 +- .github/workflows/release.yml | 5 +- script/tool/README.md | 196 +- script/tool/lib/src/analyze_command.dart | 26 +- .../tool/lib/src/build_examples_command.dart | 314 --- script/tool/lib/src/common/cmake.dart | 118 -- script/tool/lib/src/common/file_utils.dart | 20 - script/tool/lib/src/common/gradle.dart | 56 - .../lib/src/common/package_state_utils.dart | 228 --- script/tool/lib/src/common/plugin_utils.dart | 119 -- .../lib/src/common/pub_version_finder.dart | 105 - script/tool/lib/src/common/xcode.dart | 159 -- .../src/create_all_packages_app_command.dart | 344 ---- script/tool/lib/src/custom_test_command.dart | 86 - .../lib/src/dependabot_check_command.dart | 115 -- .../tool/lib/src/drive_examples_command.dart | 380 ---- .../src/federation_safety_check_command.dart | 199 -- .../lib/src/firebase_test_lab_command.dart | 358 ---- script/tool/lib/src/fix_command.dart | 51 - script/tool/lib/src/format_command.dart | 369 ---- .../tool/lib/src/license_check_command.dart | 308 --- script/tool/lib/src/lint_android_command.dart | 67 - script/tool/lib/src/list_command.dart | 68 - script/tool/lib/src/main.dart | 62 +- .../lib/src/make_deps_path_based_command.dart | 283 --- script/tool/lib/src/native_test_command.dart | 624 ------ .../tool/lib/src/podspec_check_command.dart | 194 -- .../tool/lib/src/publish_check_command.dart | 289 --- script/tool/lib/src/publish_command.dart | 456 ----- .../tool/lib/src/pubspec_check_command.dart | 397 ---- script/tool/lib/src/readme_check_command.dart | 344 ---- .../tool/lib/src/remove_dev_dependencies.dart | 58 - script/tool/lib/src/test_command.dart | 104 - .../tool/lib/src/update_excerpts_command.dart | 233 --- .../lib/src/update_release_info_command.dart | 317 --- .../tool/lib/src/version_check_command.dart | 591 ------ .../tool/lib/src/xcode_analyze_command.dart | 133 -- script/tool/pubspec.yaml | 1 + script/tool/test/analyze_command_test.dart | 425 ---- .../test/build_examples_command_test.dart | 634 ------ script/tool/test/common/file_utils_test.dart | 32 - .../test/common/git_version_finder_test.dart | 107 - script/tool/test/common/gradle_test.dart | 188 -- .../test/common/package_command_test.dart | 1151 ----------- .../common/package_command_test.mocks.dart | 286 --- .../common/package_looping_command_test.dart | 949 --------- .../test/common/package_state_utils_test.dart | 346 ---- .../tool/test/common/plugin_utils_test.dart | 256 --- .../test/common/pub_version_finder_test.dart | 89 - .../test/common/repository_package_test.dart | 220 -- script/tool/test/common/xcode_test.dart | 406 ---- .../create_all_packages_app_command_test.dart | 256 --- .../tool/test/custom_test_command_test.dart | 328 --- .../test/dependabot_check_command_test.dart | 141 -- .../test/drive_examples_command_test.dart | 1257 ------------ .../federation_safety_check_command_test.dart | 411 ---- .../test/firebase_test_lab_command_test.dart | 795 -------- script/tool/test/fix_command_test.dart | 78 - script/tool/test/format_command_test.dart | 663 ------ .../tool/test/license_check_command_test.dart | 613 ------ .../tool/test/lint_android_command_test.dart | 205 -- script/tool/test/list_command_test.dart | 197 -- .../make_deps_path_based_command_test.dart | 483 ----- script/tool/test/mocks.dart | 89 - .../tool/test/native_test_command_test.dart | 1796 ----------------- .../tool/test/podspec_check_command_test.dart | 428 ---- .../tool/test/publish_check_command_test.dart | 464 ----- script/tool/test/publish_command_test.dart | 922 --------- .../tool/test/pubspec_check_command_test.dart | 1143 ----------- .../tool/test/readme_check_command_test.dart | 741 ------- .../test/remove_dev_dependencies_test.dart | 102 - script/tool/test/test_command_test.dart | 268 --- .../test/update_excerpts_command_test.dart | 301 --- .../update_release_info_command_test.dart | 674 ------- script/tool/test/util.dart | 471 ----- .../tool/test/version_check_command_test.dart | 1468 -------------- .../tool/test/xcode_analyze_command_test.dart | 484 ----- script/tool_runner.sh | 18 +- 86 files changed, 44 insertions(+), 27661 deletions(-) delete mode 100644 .ci/scripts/plugin_tools_tests.sh delete mode 100644 .ci/targets/plugin_tools_tests.yaml delete mode 100644 script/tool/lib/src/build_examples_command.dart delete mode 100644 script/tool/lib/src/common/cmake.dart delete mode 100644 script/tool/lib/src/common/file_utils.dart delete mode 100644 script/tool/lib/src/common/gradle.dart delete mode 100644 script/tool/lib/src/common/package_state_utils.dart delete mode 100644 script/tool/lib/src/common/plugin_utils.dart delete mode 100644 script/tool/lib/src/common/pub_version_finder.dart delete mode 100644 script/tool/lib/src/common/xcode.dart delete mode 100644 script/tool/lib/src/create_all_packages_app_command.dart delete mode 100644 script/tool/lib/src/custom_test_command.dart delete mode 100644 script/tool/lib/src/dependabot_check_command.dart delete mode 100644 script/tool/lib/src/drive_examples_command.dart delete mode 100644 script/tool/lib/src/federation_safety_check_command.dart delete mode 100644 script/tool/lib/src/firebase_test_lab_command.dart delete mode 100644 script/tool/lib/src/fix_command.dart delete mode 100644 script/tool/lib/src/format_command.dart delete mode 100644 script/tool/lib/src/license_check_command.dart delete mode 100644 script/tool/lib/src/lint_android_command.dart delete mode 100644 script/tool/lib/src/list_command.dart delete mode 100644 script/tool/lib/src/make_deps_path_based_command.dart delete mode 100644 script/tool/lib/src/native_test_command.dart delete mode 100644 script/tool/lib/src/podspec_check_command.dart delete mode 100644 script/tool/lib/src/publish_check_command.dart delete mode 100644 script/tool/lib/src/publish_command.dart delete mode 100644 script/tool/lib/src/pubspec_check_command.dart delete mode 100644 script/tool/lib/src/readme_check_command.dart delete mode 100644 script/tool/lib/src/remove_dev_dependencies.dart delete mode 100644 script/tool/lib/src/test_command.dart delete mode 100644 script/tool/lib/src/update_excerpts_command.dart delete mode 100644 script/tool/lib/src/update_release_info_command.dart delete mode 100644 script/tool/lib/src/version_check_command.dart delete mode 100644 script/tool/lib/src/xcode_analyze_command.dart delete mode 100644 script/tool/test/analyze_command_test.dart delete mode 100644 script/tool/test/build_examples_command_test.dart delete mode 100644 script/tool/test/common/file_utils_test.dart delete mode 100644 script/tool/test/common/git_version_finder_test.dart delete mode 100644 script/tool/test/common/gradle_test.dart delete mode 100644 script/tool/test/common/package_command_test.dart delete mode 100644 script/tool/test/common/package_command_test.mocks.dart delete mode 100644 script/tool/test/common/package_looping_command_test.dart delete mode 100644 script/tool/test/common/package_state_utils_test.dart delete mode 100644 script/tool/test/common/plugin_utils_test.dart delete mode 100644 script/tool/test/common/pub_version_finder_test.dart delete mode 100644 script/tool/test/common/repository_package_test.dart delete mode 100644 script/tool/test/common/xcode_test.dart delete mode 100644 script/tool/test/create_all_packages_app_command_test.dart delete mode 100644 script/tool/test/custom_test_command_test.dart delete mode 100644 script/tool/test/dependabot_check_command_test.dart delete mode 100644 script/tool/test/drive_examples_command_test.dart delete mode 100644 script/tool/test/federation_safety_check_command_test.dart delete mode 100644 script/tool/test/firebase_test_lab_command_test.dart delete mode 100644 script/tool/test/fix_command_test.dart delete mode 100644 script/tool/test/format_command_test.dart delete mode 100644 script/tool/test/license_check_command_test.dart delete mode 100644 script/tool/test/lint_android_command_test.dart delete mode 100644 script/tool/test/list_command_test.dart delete mode 100644 script/tool/test/make_deps_path_based_command_test.dart delete mode 100644 script/tool/test/mocks.dart delete mode 100644 script/tool/test/native_test_command_test.dart delete mode 100644 script/tool/test/podspec_check_command_test.dart delete mode 100644 script/tool/test/publish_check_command_test.dart delete mode 100644 script/tool/test/publish_command_test.dart delete mode 100644 script/tool/test/pubspec_check_command_test.dart delete mode 100644 script/tool/test/readme_check_command_test.dart delete mode 100644 script/tool/test/remove_dev_dependencies_test.dart delete mode 100644 script/tool/test/test_command_test.dart delete mode 100644 script/tool/test/update_excerpts_command_test.dart delete mode 100644 script/tool/test/update_release_info_command_test.dart delete mode 100644 script/tool/test/util.dart delete mode 100644 script/tool/test/version_check_command_test.dart delete mode 100644 script/tool/test/xcode_analyze_command_test.dart diff --git a/.ci.yaml b/.ci.yaml index 14d1e16cd7de..6cc325b985fe 100644 --- a/.ci.yaml +++ b/.ci.yaml @@ -281,15 +281,6 @@ targets: {"dependency": "vs_build", "version": "version:vs2019"} ] - - name: Windows plugin_tools_tests - recipe: plugins/plugins - timeout: 30 - properties: - add_recipes_cq: "true" - target_file: plugin_tools_tests.yaml - channel: master - version_file: flutter_master.version - - name: Linux ci_yaml plugins roller recipe: infra/ci_yaml timeout: 30 diff --git a/.ci/scripts/build_examples_win32.sh b/.ci/scripts/build_examples_win32.sh index bcf57a4b311f..ff30ca93eec1 100644 --- a/.ci/scripts/build_examples_win32.sh +++ b/.ci/scripts/build_examples_win32.sh @@ -3,5 +3,5 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -dart ./script/tool/bin/flutter_plugin_tools.dart build-examples --windows \ +dart pub global run flutter_plugin_tools build-examples --windows \ --packages-for-branch --log-timing diff --git a/.ci/scripts/create_all_plugins_app.sh b/.ci/scripts/create_all_plugins_app.sh index 8399e5e38a35..8c45a351bef4 100644 --- a/.ci/scripts/create_all_plugins_app.sh +++ b/.ci/scripts/create_all_plugins_app.sh @@ -3,5 +3,5 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -dart ./script/tool/bin/flutter_plugin_tools.dart create-all-packages-app \ +dart pub global run flutter_plugin_tools create-all-packages-app \ --output-dir=. --exclude script/configs/exclude_all_packages_app.yaml diff --git a/.ci/scripts/drive_examples_win32.sh b/.ci/scripts/drive_examples_win32.sh index c3e2e7bc5447..d06c192ab551 100644 --- a/.ci/scripts/drive_examples_win32.sh +++ b/.ci/scripts/drive_examples_win32.sh @@ -3,5 +3,5 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -dart ./script/tool/bin/flutter_plugin_tools.dart drive-examples --windows \ +dart pub global run flutter_plugin_tools drive-examples --windows \ --exclude=script/configs/exclude_integration_win32.yaml --packages-for-branch --log-timing diff --git a/.ci/scripts/native_test_win32.sh b/.ci/scripts/native_test_win32.sh index 37cf54e55c5c..7bfe84022487 100644 --- a/.ci/scripts/native_test_win32.sh +++ b/.ci/scripts/native_test_win32.sh @@ -3,5 +3,5 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -dart ./script/tool/bin/flutter_plugin_tools.dart native-test --windows \ +dart pub global run flutter_plugin_tools native-test --windows \ --no-integration --packages-for-branch --log-timing diff --git a/.ci/scripts/plugin_tools_tests.sh b/.ci/scripts/plugin_tools_tests.sh deleted file mode 100644 index 96eec4349f08..000000000000 --- a/.ci/scripts/plugin_tools_tests.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash -# Copyright 2013 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. - -cd script/tool -dart pub run test diff --git a/.ci/scripts/prepare_tool.sh b/.ci/scripts/prepare_tool.sh index f93694bf1ff6..aced1517760c 100755 --- a/.ci/scripts/prepare_tool.sh +++ b/.ci/scripts/prepare_tool.sh @@ -6,5 +6,6 @@ # To set FETCH_HEAD for "git merge-base" to work git fetch origin main -cd script/tool -dart pub get +# Pinned version of the plugin tools, to avoid breakage in this repository +# when pushing updates from flutter/packages. +dart pub global activate flutter_plugin_tools 0.13.4+3 diff --git a/.ci/targets/plugin_tools_tests.yaml b/.ci/targets/plugin_tools_tests.yaml deleted file mode 100644 index 265e74bdd06b..000000000000 --- a/.ci/targets/plugin_tools_tests.yaml +++ /dev/null @@ -1,5 +0,0 @@ -tasks: - - name: prepare tool - script: .ci/scripts/prepare_tool.sh - - name: tool unit tests - script: .ci/scripts/plugin_tools_tests.sh diff --git a/.cirrus.yml b/.cirrus.yml index a5292c98f209..e9d513bf5d45 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -4,7 +4,7 @@ gcp_credentials: ENCRYPTED[!3a93d98d7c95a41f5033834ef30e50928fc5d81239dc632b153c only_if: $CIRRUS_TAG == '' && ($CIRRUS_PR != '' || $CIRRUS_BRANCH == 'main') env: CHANNEL: "master" # Default to master when not explicitly set by a task. - PLUGIN_TOOL_COMMAND: "dart ./script/tool/bin/flutter_plugin_tools.dart" + PLUGIN_TOOL_COMMAND: "dart pub global run flutter_plugin_tools" install_chrome_linux_template: &INSTALL_CHROME_LINUX env: @@ -77,10 +77,6 @@ task: namespace: default matrix: ### Platform-agnostic tasks ### - - name: Linux plugin_tools_tests - script: - - cd script/tool - - dart pub run test # Repository rules and best-practice enforcement. # Only channel-agnostic tests should go here since it is only run once # (on Flutter master). @@ -124,9 +120,6 @@ task: matrix: CHANNEL: "master" CHANNEL: "stable" - analyze_tool_script: - - cd script/tool - - dart analyze --fatal-infos analyze_script: # DO NOT change the custom-analysis argument here without changing the Dart repo. # See the comment in script/configs/custom_analysis.yaml for details. diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bbb153386ff2..532987f931df 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -31,8 +31,7 @@ jobs: with: fetch-depth: 0 # Fetch all history so the tool can get all the tags to determine version. - name: Set up tools - run: dart pub get - working-directory: ${{ github.workspace }}/script/tool + run: dart pub global activate flutter_plugin_tools 0.13.4+3 # This workflow should be the last to run. So wait for all the other tests to succeed. - name: Wait on all tests @@ -50,5 +49,5 @@ jobs: run: | git config --global user.name ${{ secrets.USER_NAME }} git config --global user.email ${{ secrets.USER_EMAIL }} - dart ./script/tool/lib/src/main.dart publish --all-changed --base-sha=HEAD~ --skip-confirmation --remote=origin + dart pub global run flutter_plugin_tools publish --all-changed --base-sha=HEAD~ --skip-confirmation --remote=origin env: {PUB_CREDENTIALS: "${{ secrets.PUB_CREDENTIALS }}"} diff --git a/script/tool/README.md b/script/tool/README.md index 9f0ac84145f2..aa4c0517ce71 100644 --- a/script/tool/README.md +++ b/script/tool/README.md @@ -1,191 +1,13 @@ -# Flutter Plugin Tools +# Removed -This is a set of utilities used in the flutter/plugins and flutter/packages -repositories. It is no longer explictily maintained as a general-purpose tool -for multi-package repositories, so your mileage may vary if using it in other -repositories. +See https://github.com/flutter/packages/blob/main/script/tool/README.md for the +current location of this tooling. -Note: The commands in tools are designed to run at the root of the repository or `/packages/`. +## Temporary shim -## Getting Started +This is a temporary, minimal version of the tools sufficient to keep the +following scripts running until the repository merge is complete and they are +updated to use flutter/packages instead: -In flutter/plugins, the tool is run from source. In flutter/packages, the -[published version](https://pub.dev/packages/flutter_plugin_tools) is used -instead. (It is marked as Discontinued since it is no longer maintained as -a general-purpose tool, but updates are still published for use in -flutter/packages.) - -The commands in tools require the Flutter-bundled version of Dart to be the first `dart` loaded in the path. - -### Extra Setup - -When updating sample code excerpts (`update-excerpts`) for the README.md files, -there is some [extra setup for -submodules](#update-readmemd-from-example-sources) that is necessary. - -### From Source (flutter/plugins only) - -Set up: - -```sh -cd ./script/tool && dart pub get && cd ../../ -``` - -Run: - -```sh -dart run ./script/tool/bin/flutter_plugin_tools.dart -``` - -### Published Version - -Set up: - -```sh -dart pub global activate flutter_plugin_tools -``` - -Run: - -```sh -dart pub global run flutter_plugin_tools -``` - -## Commands - -Run with `--help` for a full list of commands and arguments, but the -following shows a number of common commands being run for a specific package. - -All examples assume running from source; see above for running the -published version instead. - -Most commands take a `--packages` argument to control which package(s) the -command is targetting. An package name can be any of: -- The name of a package (e.g., `path_provider_android`). -- The name of a federated plugin (e.g., `path_provider`), in which case all - packages that make up that plugin will be targetted. -- A combination federated_plugin_name/package_name (e.g., - `path_provider/path_provider` for the app-facing package). - -### Format Code - -```sh -cd -dart run ./script/tool/bin/flutter_plugin_tools.dart format --packages package_name -``` - -### Run the Dart Static Analyzer - -```sh -cd -dart run ./script/tool/bin/flutter_plugin_tools.dart analyze --packages package_name -``` - -### Run Dart Unit Tests - -```sh -cd -dart run ./script/tool/bin/flutter_plugin_tools.dart test --packages package_name -``` - -### Run Dart Integration Tests - -```sh -cd -dart run ./script/tool/bin/flutter_plugin_tools.dart build-examples --apk --packages package_name -dart run ./script/tool/bin/flutter_plugin_tools.dart drive-examples --android --packages package_name -``` - -Replace `--apk`/`--android` with the platform you want to test against -(omit it to get a list of valid options). - -### Run Native Tests - -`native-test` takes one or more platform flags to run tests for. By default it -runs both unit tests and (on platforms that support it) integration tests, but -`--no-unit` or `--no-integration` can be used to run just one type. - -Examples: - -```sh -cd -# Run just unit tests for iOS and Android: -dart run ./script/tool/bin/flutter_plugin_tools.dart native-test --ios --android --no-integration --packages package_name -# Run all tests for macOS: -dart run ./script/tool/bin/flutter_plugin_tools.dart native-test --macos --packages package_name -# Run all tests for Windows: -dart run ./script/tool/bin/flutter_plugin_tools.dart native-test --windows --packages package_name -``` - -### Update README.md from Example Sources - -`update-excerpts` requires sources that are in a submodule. If you didn't clone -with submodules, you will need to `git submodule update --init --recursive` -before running this command. - -```sh -cd -dart run ./script/tool/bin/flutter_plugin_tools.dart update-excerpts --packages package_name -``` - -### Update CHANGELOG and Version - -`update-release-info` will automatically update the version and `CHANGELOG.md` -following standard repository style and practice. It can be used for -single-package updates to handle the details of getting the `CHANGELOG.md` -format correct, but is especially useful for bulk updates across multiple packages. - -For instance, if you add a new analysis option that requires production -code changes across many packages: - -```sh -cd -dart run ./script/tool/bin/flutter_plugin_tools.dart update-release-info \ - --version=minimal \ - --changelog="Fixes violations of new analysis option some_new_option." -``` - -The `minimal` option for `--version` will skip unchanged packages, and treat -each changed package as either `bugfix` or `next` depending on the files that -have changed in that package, so it is often the best choice for a bulk change. - -For cases where you know the change time, `minor` or `bugfix` will make the -corresponding version bump, or `next` will update only `CHANGELOG.md` without -changing the version. - -### Publish a Release - -**Releases are automated for `flutter/plugins` and `flutter/packages`.** - -The manual procedure described here is _deprecated_, and should only be used when -the automated process fails. Please, read -[Releasing a Plugin or Package](https://github.com/flutter/flutter/wiki/Releasing-a-Plugin-or-Package) -on the Flutter Wiki first. - -```sh -cd -git checkout -dart run ./script/tool/bin/flutter_plugin_tools.dart publish --packages -``` - -By default the tool tries to push tags to the `upstream` remote, but some -additional settings can be configured. Run `dart run ./script/tool/bin/flutter_plugin_tools.dart -publish --help` for more usage information. - -The tool wraps `pub publish` for pushing the package to pub, and then will -automatically use git to try to 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. -`publish` will first check the status of the local -directory and refuse to publish if there are any mismatched files with version -control present. - -## Updating the Tool - -For flutter/plugins, just changing the source here is all that's needed. - -For changes that are relevant to flutter/packages, you will also need to: -- Update the tool's pubspec.yaml and CHANGELOG -- Publish the tool -- Update the pinned version in - [flutter/packages](https://github.com/flutter/packages/blob/main/.cirrus.yml) +- [dart-lang analysis](https://github.com/dart-lang/sdk/blob/main/tools/bots/flutter/analyze_flutter_plugins.sh) +- [flutter/flutter analysis](https://github.com/flutter/flutter/blob/master/dev/bots/test.dart) diff --git a/script/tool/lib/src/analyze_command.dart b/script/tool/lib/src/analyze_command.dart index c7a953c50cac..3d9e4e5c9802 100644 --- a/script/tool/lib/src/analyze_command.dart +++ b/script/tool/lib/src/analyze_command.dart @@ -32,17 +32,9 @@ class AnalyzeCommand extends PackageLoopingCommand { valueHelp: 'dart-sdk', help: 'An optional path to a Dart SDK; this is used to override the ' 'SDK used to provide analysis.'); - argParser.addFlag(_downgradeFlag, - help: 'Runs "flutter pub downgrade" before analysis to verify that ' - 'the minimum constraints are sufficiently new for APIs used.'); - argParser.addFlag(_libOnlyFlag, - help: 'Only analyze the lib/ directory of the main package, not the ' - 'entire package.'); } static const String _customAnalysisFlag = 'custom-analysis'; - static const String _downgradeFlag = 'downgrade'; - static const String _libOnlyFlag = 'lib-only'; static const String _analysisSdk = 'analysis-sdk'; late String _dartBinaryPath; @@ -111,18 +103,6 @@ class AnalyzeCommand extends PackageLoopingCommand { @override Future runForPackage(RepositoryPackage package) async { - final bool libOnly = getBoolArg(_libOnlyFlag); - - if (libOnly && !package.libDirectory.existsSync()) { - return PackageResult.skip('No lib/ directory.'); - } - - if (getBoolArg(_downgradeFlag)) { - if (!await _runPubCommand(package, 'downgrade')) { - return PackageResult.fail(['Unable to downgrade dependencies']); - } - } - // Analysis runs over the package and all subpackages (unless only lib/ is // being analyzed), so all of them need `flutter pub get` run before // analyzing. `example` packages can be skipped since 'flutter packages get' @@ -130,7 +110,7 @@ class AnalyzeCommand extends PackageLoopingCommand { // directory. final List packagesToGet = [ package, - if (!libOnly) ...await getSubpackages(package).toList(), + ...await getSubpackages(package).toList(), ]; for (final RepositoryPackage packageToGet in packagesToGet) { if (packageToGet.directory.basename != 'example' || @@ -146,8 +126,8 @@ class AnalyzeCommand extends PackageLoopingCommand { if (_hasUnexpecetdAnalysisOptions(package)) { return PackageResult.fail(['Unexpected local analysis options']); } - final int exitCode = await processRunner.runAndStream(_dartBinaryPath, - ['analyze', '--fatal-infos', if (libOnly) 'lib'], + final int exitCode = await processRunner.runAndStream( + _dartBinaryPath, ['analyze', '--fatal-infos'], workingDir: package.directory); if (exitCode != 0) { return PackageResult.fail(); diff --git a/script/tool/lib/src/build_examples_command.dart b/script/tool/lib/src/build_examples_command.dart deleted file mode 100644 index 1aade3575559..000000000000 --- a/script/tool/lib/src/build_examples_command.dart +++ /dev/null @@ -1,314 +0,0 @@ -// Copyright 2013 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. - -import 'dart:async'; - -import 'package:file/file.dart'; -import 'package:platform/platform.dart'; -import 'package:yaml/yaml.dart'; - -import 'common/core.dart'; -import 'common/package_looping_command.dart'; -import 'common/plugin_utils.dart'; -import 'common/process_runner.dart'; -import 'common/repository_package.dart'; - -/// Key for APK. -const String _platformFlagApk = 'apk'; - -const String _pluginToolsConfigFileName = '.pluginToolsConfig.yaml'; -const String _pluginToolsConfigBuildFlagsKey = 'buildFlags'; -const String _pluginToolsConfigGlobalKey = 'global'; - -const String _pluginToolsConfigExample = ''' -$_pluginToolsConfigBuildFlagsKey: - $_pluginToolsConfigGlobalKey: - - "--no-tree-shake-icons" - - "--dart-define=buildmode=testing" -'''; - -const int _exitNoPlatformFlags = 3; -const int _exitInvalidPluginToolsConfig = 4; - -// Flutter build types. These are the values passed to `flutter build `. -const String _flutterBuildTypeAndroid = 'apk'; -const String _flutterBuildTypeIOS = 'ios'; -const String _flutterBuildTypeLinux = 'linux'; -const String _flutterBuildTypeMacOS = 'macos'; -const String _flutterBuildTypeWeb = 'web'; -const String _flutterBuildTypeWindows = 'windows'; - -/// A command to build the example applications for packages. -class BuildExamplesCommand extends PackageLoopingCommand { - /// Creates an instance of the build command. - BuildExamplesCommand( - Directory packagesDir, { - ProcessRunner processRunner = const ProcessRunner(), - Platform platform = const LocalPlatform(), - }) : super(packagesDir, processRunner: processRunner, platform: platform) { - argParser.addFlag(platformLinux); - argParser.addFlag(platformMacOS); - argParser.addFlag(platformWeb); - argParser.addFlag(platformWindows); - argParser.addFlag(platformIOS); - argParser.addFlag(_platformFlagApk); - argParser.addOption( - kEnableExperiment, - defaultsTo: '', - help: 'Enables the given Dart SDK experiments.', - ); - } - - // Maps the switch this command uses to identify a platform to information - // about it. - static final Map _platforms = - { - _platformFlagApk: const _PlatformDetails( - 'Android', - pluginPlatform: platformAndroid, - flutterBuildType: _flutterBuildTypeAndroid, - ), - platformIOS: const _PlatformDetails( - 'iOS', - pluginPlatform: platformIOS, - flutterBuildType: _flutterBuildTypeIOS, - extraBuildFlags: ['--no-codesign'], - ), - platformLinux: const _PlatformDetails( - 'Linux', - pluginPlatform: platformLinux, - flutterBuildType: _flutterBuildTypeLinux, - ), - platformMacOS: const _PlatformDetails( - 'macOS', - pluginPlatform: platformMacOS, - flutterBuildType: _flutterBuildTypeMacOS, - ), - platformWeb: const _PlatformDetails( - 'web', - pluginPlatform: platformWeb, - flutterBuildType: _flutterBuildTypeWeb, - ), - platformWindows: const _PlatformDetails( - 'Windows', - pluginPlatform: platformWindows, - flutterBuildType: _flutterBuildTypeWindows, - ), - }; - - @override - final String name = 'build-examples'; - - @override - final String description = - 'Builds all example apps (IPA for iOS and APK for Android).\n\n' - 'This command requires "flutter" to be in your path.\n\n' - 'A $_pluginToolsConfigFileName file can be placed in an example app ' - 'directory to specify additional build arguments. It should be a YAML ' - 'file with a top-level map containing a single key ' - '"$_pluginToolsConfigBuildFlagsKey" containing a map containing a ' - 'single key "$_pluginToolsConfigGlobalKey" containing a list of build ' - 'arguments.'; - - @override - Future initializeRun() async { - final List platformFlags = _platforms.keys.toList(); - platformFlags.sort(); - if (!platformFlags.any((String platform) => getBoolArg(platform))) { - printError( - 'None of ${platformFlags.map((String platform) => '--$platform').join(', ')} ' - 'were specified. At least one platform must be provided.'); - throw ToolExit(_exitNoPlatformFlags); - } - } - - @override - Future runForPackage(RepositoryPackage package) async { - final List errors = []; - - final bool isPlugin = isFlutterPlugin(package); - final Iterable<_PlatformDetails> requestedPlatforms = _platforms.entries - .where( - (MapEntry entry) => getBoolArg(entry.key)) - .map((MapEntry entry) => entry.value); - - // Platform support is checked at the package level for plugins; there is - // no package-level platform information for non-plugin packages. - final Set<_PlatformDetails> buildPlatforms = isPlugin - ? requestedPlatforms - .where((_PlatformDetails platform) => - pluginSupportsPlatform(platform.pluginPlatform, package)) - .toSet() - : requestedPlatforms.toSet(); - - String platformDisplayList(Iterable<_PlatformDetails> platforms) { - return platforms.map((_PlatformDetails p) => p.label).join(', '); - } - - if (buildPlatforms.isEmpty) { - final String unsupported = requestedPlatforms.length == 1 - ? '${requestedPlatforms.first.label} is not supported' - : 'None of [${platformDisplayList(requestedPlatforms)}] are supported'; - return PackageResult.skip('$unsupported by this plugin'); - } - print('Building for: ${platformDisplayList(buildPlatforms)}'); - - final Set<_PlatformDetails> unsupportedPlatforms = - requestedPlatforms.toSet().difference(buildPlatforms); - if (unsupportedPlatforms.isNotEmpty) { - final List skippedPlatforms = unsupportedPlatforms - .map((_PlatformDetails platform) => platform.label) - .toList(); - skippedPlatforms.sort(); - print('Skipping unsupported platform(s): ' - '${skippedPlatforms.join(', ')}'); - } - print(''); - - bool builtSomething = false; - for (final RepositoryPackage example in package.getExamples()) { - final String packageName = - getRelativePosixPath(example.directory, from: packagesDir); - - for (final _PlatformDetails platform in buildPlatforms) { - // Repo policy is that a plugin must have examples configured for all - // supported platforms. For packages, just log and skip any requested - // platform that a package doesn't have set up. - if (!isPlugin && - !example.directory - .childDirectory(platform.flutterPlatformDirectory) - .existsSync()) { - print('Skipping ${platform.label} for $packageName; not supported.'); - continue; - } - - builtSomething = true; - - String buildPlatform = platform.label; - if (platform.label.toLowerCase() != platform.flutterBuildType) { - buildPlatform += ' (${platform.flutterBuildType})'; - } - print('\nBUILDING $packageName for $buildPlatform'); - if (!await _buildExample(example, platform.flutterBuildType, - extraBuildFlags: platform.extraBuildFlags)) { - errors.add('$packageName (${platform.label})'); - } - } - } - - if (!builtSomething) { - if (isPlugin) { - errors.add('No examples found'); - } else { - return PackageResult.skip( - 'No examples found supporting requested platform(s).'); - } - } - - return errors.isEmpty - ? PackageResult.success() - : PackageResult.fail(errors); - } - - Iterable _readExtraBuildFlagsConfiguration( - Directory directory) sync* { - final File pluginToolsConfig = - directory.childFile(_pluginToolsConfigFileName); - if (pluginToolsConfig.existsSync()) { - final Object? configuration = - loadYaml(pluginToolsConfig.readAsStringSync()); - if (configuration is! YamlMap) { - printError('The $_pluginToolsConfigFileName file must be a YAML map.'); - printError( - 'Currently, the key "$_pluginToolsConfigBuildFlagsKey" is the only one that has an effect.'); - printError( - 'It must itself be a map. Currently, in that map only the key "$_pluginToolsConfigGlobalKey"'); - printError( - 'has any effect; it must contain a list of arguments to pass to the'); - printError('flutter tool.'); - printError(_pluginToolsConfigExample); - throw ToolExit(_exitInvalidPluginToolsConfig); - } - if (configuration.containsKey(_pluginToolsConfigBuildFlagsKey)) { - final Object? buildFlagsConfiguration = - configuration[_pluginToolsConfigBuildFlagsKey]; - if (buildFlagsConfiguration is! YamlMap) { - printError( - 'The $_pluginToolsConfigFileName file\'s "$_pluginToolsConfigBuildFlagsKey" key must be a map.'); - printError( - 'Currently, in that map only the key "$_pluginToolsConfigGlobalKey" has any effect; it must '); - printError( - 'contain a list of arguments to pass to the flutter tool.'); - printError(_pluginToolsConfigExample); - throw ToolExit(_exitInvalidPluginToolsConfig); - } - if (buildFlagsConfiguration.containsKey(_pluginToolsConfigGlobalKey)) { - final Object? globalBuildFlagsConfiguration = - buildFlagsConfiguration[_pluginToolsConfigGlobalKey]; - if (globalBuildFlagsConfiguration is! YamlList) { - printError( - 'The $_pluginToolsConfigFileName file\'s "$_pluginToolsConfigBuildFlagsKey" key must be a map'); - printError('whose "$_pluginToolsConfigGlobalKey" key is a list.'); - printError( - 'That list must contain a list of arguments to pass to the flutter tool.'); - printError( - 'For example, the $_pluginToolsConfigFileName file could look like:'); - printError(_pluginToolsConfigExample); - throw ToolExit(_exitInvalidPluginToolsConfig); - } - yield* globalBuildFlagsConfiguration.cast(); - } - } - } - } - - Future _buildExample( - RepositoryPackage example, - String flutterBuildType, { - List extraBuildFlags = const [], - }) async { - final String enableExperiment = getStringArg(kEnableExperiment); - - final int exitCode = await processRunner.runAndStream( - flutterCommand, - [ - 'build', - flutterBuildType, - ...extraBuildFlags, - ..._readExtraBuildFlagsConfiguration(example.directory), - if (enableExperiment.isNotEmpty) - '--enable-experiment=$enableExperiment', - ], - workingDir: example.directory, - ); - return exitCode == 0; - } -} - -/// A collection of information related to a specific platform. -class _PlatformDetails { - const _PlatformDetails( - this.label, { - required this.pluginPlatform, - required this.flutterBuildType, - this.extraBuildFlags = const [], - }); - - /// The name to use in output. - final String label; - - /// The key in a pubspec's platform: entry. - final String pluginPlatform; - - /// The `flutter build` build type. - final String flutterBuildType; - - /// The Flutter platform directory name. - // In practice, this is the same as the plugin platform key for all platforms. - // If that changes, this can be adjusted. - String get flutterPlatformDirectory => pluginPlatform; - - /// Any extra flags to pass to `flutter build`. - final List extraBuildFlags; -} diff --git a/script/tool/lib/src/common/cmake.dart b/script/tool/lib/src/common/cmake.dart deleted file mode 100644 index 3f5d8452bd44..000000000000 --- a/script/tool/lib/src/common/cmake.dart +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright 2013 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. - -import 'package:file/file.dart'; -import 'package:platform/platform.dart'; - -import 'core.dart'; -import 'process_runner.dart'; - -const String _cacheCommandKey = 'CMAKE_COMMAND:INTERNAL'; - -/// A utility class for interacting with CMake projects. -class CMakeProject { - /// Creates an instance that runs commands for [project] with the given - /// [processRunner]. - CMakeProject( - this.flutterProject, { - required this.buildMode, - this.processRunner = const ProcessRunner(), - this.platform = const LocalPlatform(), - }); - - /// The directory of a Flutter project to run Gradle commands in. - final Directory flutterProject; - - /// The [ProcessRunner] used to run commands. Overridable for testing. - final ProcessRunner processRunner; - - /// The platform that commands are being run on. - final Platform platform; - - /// The build mode (e.g., Debug, Release). - /// - /// This is a constructor paramater because on Linux many properties depend - /// on the build mode since it uses a single-configuration generator. - final String buildMode; - - late final String _cmakeCommand = _determineCmakeCommand(); - - /// The project's platform directory name. - String get _platformDirName => platform.isWindows ? 'windows' : 'linux'; - - /// The project's 'example' build directory for this instance's platform. - Directory get buildDirectory { - Directory buildDir = - flutterProject.childDirectory('build').childDirectory(_platformDirName); - if (platform.isLinux) { - buildDir = buildDir - // TODO(stuartmorgan): Support arm64 if that ever becomes a supported - // CI configuration for the repository. - .childDirectory('x64') - // Linux uses a single-config generator, so the base build directory - // includes the configuration. - .childDirectory(buildMode.toLowerCase()); - } - return buildDir; - } - - File get _cacheFile => buildDirectory.childFile('CMakeCache.txt'); - - /// Returns the CMake command to run build commands for this project. - /// - /// Assumes the project has been built at least once, such that the CMake - /// generation step has run. - String getCmakeCommand() { - return _cmakeCommand; - } - - /// Returns the CMake command to run build commands for this project. This is - /// used to initialize _cmakeCommand, and should not be called directly. - /// - /// Assumes the project has been built at least once, such that the CMake - /// generation step has run. - String _determineCmakeCommand() { - // On Linux 'cmake' is expected to be in the path, so doesn't need to - // be lookup up and cached. - if (platform.isLinux) { - return 'cmake'; - } - final File cacheFile = _cacheFile; - String? command; - for (String line in cacheFile.readAsLinesSync()) { - line = line.trim(); - if (line.startsWith(_cacheCommandKey)) { - command = line.substring(line.indexOf('=') + 1).trim(); - break; - } - } - if (command == null) { - printError('Unable to find CMake command in ${cacheFile.path}'); - throw ToolExit(100); - } - return command; - } - - /// Whether or not the project is ready to have CMake commands run on it - /// (i.e., whether the `flutter` tool has generated the necessary files). - bool isConfigured() => _cacheFile.existsSync(); - - /// Runs a `cmake` command with the given parameters. - Future runBuild( - String target, { - List arguments = const [], - }) { - return processRunner.runAndStream( - getCmakeCommand(), - [ - '--build', - buildDirectory.path, - '--target', - target, - if (platform.isWindows) ...['--config', buildMode], - ...arguments, - ], - ); - } -} diff --git a/script/tool/lib/src/common/file_utils.dart b/script/tool/lib/src/common/file_utils.dart deleted file mode 100644 index 3c2f2f18f954..000000000000 --- a/script/tool/lib/src/common/file_utils.dart +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2013 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. - -import 'package:file/file.dart'; - -/// Returns a [File] created by appending all but the last item in [components] -/// to [base] as subdirectories, then appending the last as a file. -/// -/// Example: -/// childFileWithSubcomponents(rootDir, ['foo', 'bar', 'baz.txt']) -/// creates a File representing /rootDir/foo/bar/baz.txt. -File childFileWithSubcomponents(Directory base, List components) { - Directory dir = base; - final String basename = components.removeLast(); - for (final String directoryName in components) { - dir = dir.childDirectory(directoryName); - } - return dir.childFile(basename); -} diff --git a/script/tool/lib/src/common/gradle.dart b/script/tool/lib/src/common/gradle.dart deleted file mode 100644 index 746536075014..000000000000 --- a/script/tool/lib/src/common/gradle.dart +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2013 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. - -import 'package:file/file.dart'; -import 'package:platform/platform.dart'; - -import 'process_runner.dart'; -import 'repository_package.dart'; - -const String _gradleWrapperWindows = 'gradlew.bat'; -const String _gradleWrapperNonWindows = 'gradlew'; - -/// A utility class for interacting with Gradle projects. -class GradleProject { - /// Creates an instance that runs commands for [project] with the given - /// [processRunner]. - GradleProject( - this.flutterProject, { - this.processRunner = const ProcessRunner(), - this.platform = const LocalPlatform(), - }); - - /// The directory of a Flutter project to run Gradle commands in. - final RepositoryPackage flutterProject; - - /// The [ProcessRunner] used to run commands. Overridable for testing. - final ProcessRunner processRunner; - - /// The platform that commands are being run on. - final Platform platform; - - /// The project's 'android' directory. - Directory get androidDirectory => - flutterProject.platformDirectory(FlutterPlatform.android); - - /// The path to the Gradle wrapper file for the project. - File get gradleWrapper => androidDirectory.childFile( - platform.isWindows ? _gradleWrapperWindows : _gradleWrapperNonWindows); - - /// Whether or not the project is ready to have Gradle commands run on it - /// (i.e., whether the `flutter` tool has generated the necessary files). - bool isConfigured() => gradleWrapper.existsSync(); - - /// Runs a `gradlew` command with the given parameters. - Future runCommand( - String target, { - List arguments = const [], - }) { - return processRunner.runAndStream( - gradleWrapper.path, - [target, ...arguments], - workingDir: androidDirectory, - ); - } -} diff --git a/script/tool/lib/src/common/package_state_utils.dart b/script/tool/lib/src/common/package_state_utils.dart deleted file mode 100644 index fbba75c6116f..000000000000 --- a/script/tool/lib/src/common/package_state_utils.dart +++ /dev/null @@ -1,228 +0,0 @@ -// Copyright 2013 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. - -import 'package:file/file.dart'; -import 'package:meta/meta.dart'; -import 'package:path/path.dart' as p; - -import 'git_version_finder.dart'; -import 'repository_package.dart'; - -/// The state of a package on disk relative to git state. -@immutable -class PackageChangeState { - /// Creates a new immutable state instance. - const PackageChangeState({ - required this.hasChanges, - required this.hasChangelogChange, - required this.needsChangelogChange, - required this.needsVersionChange, - }); - - /// True if there are any changes to files in the package. - final bool hasChanges; - - /// True if the package's CHANGELOG.md has been changed. - final bool hasChangelogChange; - - /// True if any changes in the package require a version change according - /// to repository policy. - final bool needsVersionChange; - - /// True if any changes in the package require a CHANGELOG change according - /// to repository policy. - final bool needsChangelogChange; -} - -/// Checks [package] against [changedPaths] to determine what changes it has -/// and how those changes relate to repository policy about CHANGELOG and -/// version updates. -/// -/// [changedPaths] should be a list of POSIX-style paths from a common root, -/// and [relativePackagePath] should be the path to [package] from that same -/// root. Commonly these will come from `gitVersionFinder.getChangedFiles()` -/// and `getRelativePosixPath(package.directory, gitDir.path)` respectively; -/// they are arguments mainly to allow for caching the changed paths for an -/// entire command run. -/// -/// If [git] is provided, [changedPaths] must be repository-relative -/// paths, and change type detection can use file diffs in addition to paths. -Future checkPackageChangeState( - RepositoryPackage package, { - required List changedPaths, - required String relativePackagePath, - GitVersionFinder? git, -}) async { - final String packagePrefix = relativePackagePath.endsWith('/') - ? relativePackagePath - : '$relativePackagePath/'; - - bool hasChanges = false; - bool hasChangelogChange = false; - bool needsVersionChange = false; - bool needsChangelogChange = false; - for (final String path in changedPaths) { - // Only consider files within the package. - if (!path.startsWith(packagePrefix)) { - continue; - } - final String packageRelativePath = path.substring(packagePrefix.length); - hasChanges = true; - - final List components = p.posix.split(packageRelativePath); - if (components.isEmpty) { - continue; - } - - if (components.first == 'CHANGELOG.md') { - hasChangelogChange = true; - continue; - } - - if (!needsVersionChange) { - // Developer-only changes don't need version changes or changelog changes. - if (await _isDevChange(components, git: git, repoPath: path)) { - continue; - } - - // Some other changes don't need version changes, but might benefit from - // changelog changes. - needsChangelogChange = true; - if ( - // One of a few special files example will be shown on pub.dev, but - // for anything else in the example publishing has no purpose. - !_isUnpublishedExampleChange(components, package)) { - needsVersionChange = true; - } - } - } - - return PackageChangeState( - hasChanges: hasChanges, - hasChangelogChange: hasChangelogChange, - needsChangelogChange: needsChangelogChange, - needsVersionChange: needsVersionChange); -} - -bool _isTestChange(List pathComponents) { - return pathComponents.contains('test') || - pathComponents.contains('integration_test') || - pathComponents.contains('androidTest') || - pathComponents.contains('RunnerTests') || - pathComponents.contains('RunnerUITests') || - // Pigeon's custom platform tests. - pathComponents.first == 'platform_tests'; -} - -// True if the given file is an example file other than the one that will be -// published according to https://dart.dev/tools/pub/package-layout#examples. -// -// This is not exhastive; it currently only handles variations we actually have -// in our repositories. -bool _isUnpublishedExampleChange( - List pathComponents, RepositoryPackage package) { - if (pathComponents.first != 'example') { - return false; - } - final List exampleComponents = pathComponents.sublist(1); - if (exampleComponents.isEmpty) { - return false; - } - - final Directory exampleDirectory = - package.directory.childDirectory('example'); - - // Check for example.md/EXAMPLE.md first, as that has priority. If it's - // present, any other example file is unpublished. - final bool hasExampleMd = - exampleDirectory.childFile('example.md').existsSync() || - exampleDirectory.childFile('EXAMPLE.md').existsSync(); - if (hasExampleMd) { - return !(exampleComponents.length == 1 && - exampleComponents.first.toLowerCase() == 'example.md'); - } - - // Most packages have an example/lib/main.dart (or occasionally - // example/main.dart), so check for that. The other naming variations aren't - // currently used. - const String mainName = 'main.dart'; - final bool hasExampleCode = - exampleDirectory.childDirectory('lib').childFile(mainName).existsSync() || - exampleDirectory.childFile(mainName).existsSync(); - if (hasExampleCode) { - // If there is an example main, only that example file is published. - return !((exampleComponents.length == 1 && - exampleComponents.first == mainName) || - (exampleComponents.length == 2 && - exampleComponents.first == 'lib' && - exampleComponents[1] == mainName)); - } - - // If there's no example code either, the example README.md, if any, is the - // file that will be published. - return exampleComponents.first.toLowerCase() != 'readme.md'; -} - -// True if the change is only relevant to people working on the package. -Future _isDevChange(List pathComponents, - {GitVersionFinder? git, String? repoPath}) async { - return _isTestChange(pathComponents) || - // The top-level "tool" directory is for non-client-facing utility - // code, such as test scripts. - pathComponents.first == 'tool' || - // The top-level "pigeons" directory is the repo convention for storing - // pigeon input files. - pathComponents.first == 'pigeons' || - // Entry point for the 'custom-test' command, which is only for CI and - // local testing. - pathComponents.first == 'run_tests.sh' || - // Ignoring lints doesn't affect clients. - pathComponents.contains('lint-baseline.xml') || - // Example build files are very unlikely to be interesting to clients. - _isExampleBuildFile(pathComponents) || - // Test-only gradle depenedencies don't affect clients. - await _isGradleTestDependencyChange(pathComponents, - git: git, repoPath: repoPath); -} - -bool _isExampleBuildFile(List pathComponents) { - if (!pathComponents.contains('example')) { - return false; - } - return pathComponents.contains('gradle-wrapper.properties') || - pathComponents.contains('gradle.properties') || - pathComponents.contains('build.gradle') || - pathComponents.contains('Runner.xcodeproj') || - pathComponents.contains('CMakeLists.txt') || - pathComponents.contains('pubspec.yaml'); -} - -Future _isGradleTestDependencyChange(List pathComponents, - {GitVersionFinder? git, String? repoPath}) async { - if (git == null) { - return false; - } - if (pathComponents.last != 'build.gradle') { - return false; - } - final List diff = await git.getDiffContents(targetPath: repoPath); - final RegExp changeLine = RegExp(r'[+-] '); - final RegExp testDependencyLine = - RegExp(r'[+-]\s*(?:androidT|t)estImplementation\s'); - bool foundTestDependencyChange = false; - for (final String line in diff) { - if (!changeLine.hasMatch(line) || - line.startsWith('--- ') || - line.startsWith('+++ ')) { - continue; - } - if (!testDependencyLine.hasMatch(line)) { - return false; - } - foundTestDependencyChange = true; - } - // Only return true if a test dependency change was found, as a failsafe - // against having the wrong (e.g., incorrectly empty) diff output. - return foundTestDependencyChange; -} diff --git a/script/tool/lib/src/common/plugin_utils.dart b/script/tool/lib/src/common/plugin_utils.dart deleted file mode 100644 index 94677fe7e5a3..000000000000 --- a/script/tool/lib/src/common/plugin_utils.dart +++ /dev/null @@ -1,119 +0,0 @@ -// Copyright 2013 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. - -import 'package:yaml/yaml.dart'; - -import 'core.dart'; -import 'repository_package.dart'; - -/// Possible plugin support options for a platform. -enum PlatformSupport { - /// The platform has an implementation in the package. - inline, - - /// The platform has an endorsed federated implementation in another package. - federated, -} - -/// Returns true if [package] is a Flutter plugin. -bool isFlutterPlugin(RepositoryPackage package) { - return _readPluginPubspecSection(package) != null; -} - -/// Returns true if [package] is a Flutter [platform] plugin. -/// -/// It checks this by looking for the following pattern in the pubspec: -/// -/// flutter: -/// plugin: -/// platforms: -/// [platform]: -/// -/// If [requiredMode] is provided, the plugin must have the given type of -/// implementation in order to return true. -bool pluginSupportsPlatform( - String platform, - RepositoryPackage plugin, { - PlatformSupport? requiredMode, -}) { - assert(platform == platformIOS || - platform == platformAndroid || - platform == platformWeb || - platform == platformMacOS || - platform == platformWindows || - platform == platformLinux); - - final YamlMap? platformEntry = - _readPlatformPubspecSectionForPlugin(platform, plugin); - if (platformEntry == null) { - return false; - } - - // If the platform entry is present, then it supports the platform. Check - // for required mode if specified. - if (requiredMode != null) { - final bool federated = platformEntry.containsKey('default_package'); - if (federated != (requiredMode == PlatformSupport.federated)) { - return false; - } - } - - return true; -} - -/// Returns true if [plugin] includes native code for [platform], as opposed to -/// being implemented entirely in Dart. -bool pluginHasNativeCodeForPlatform(String platform, RepositoryPackage plugin) { - if (platform == platformWeb) { - // Web plugins are always Dart-only. - return false; - } - final YamlMap? platformEntry = - _readPlatformPubspecSectionForPlugin(platform, plugin); - if (platformEntry == null) { - return false; - } - // All other platforms currently use pluginClass for indicating the native - // code in the plugin. - final String? pluginClass = platformEntry['pluginClass'] as String?; - // TODO(stuartmorgan): Remove the check for 'none' once none of the plugins - // in the repository use that workaround. See - // https://github.com/flutter/flutter/issues/57497 for context. - return pluginClass != null && pluginClass != 'none'; -} - -/// Returns the -/// flutter: -/// plugin: -/// platforms: -/// [platform]: -/// section from [plugin]'s pubspec.yaml, or null if either it is not present, -/// or the pubspec couldn't be read. -YamlMap? _readPlatformPubspecSectionForPlugin( - String platform, RepositoryPackage plugin) { - final YamlMap? pluginSection = _readPluginPubspecSection(plugin); - if (pluginSection == null) { - return null; - } - final YamlMap? platforms = pluginSection['platforms'] as YamlMap?; - if (platforms == null) { - return null; - } - return platforms[platform] as YamlMap?; -} - -/// Returns the -/// flutter: -/// plugin: -/// platforms: -/// section from [plugin]'s pubspec.yaml, or null if either it is not present, -/// or the pubspec couldn't be read. -YamlMap? _readPluginPubspecSection(RepositoryPackage package) { - final Pubspec pubspec = package.parsePubspec(); - final Map? flutterSection = pubspec.flutter; - if (flutterSection == null) { - return null; - } - return flutterSection['plugin'] as YamlMap?; -} diff --git a/script/tool/lib/src/common/pub_version_finder.dart b/script/tool/lib/src/common/pub_version_finder.dart deleted file mode 100644 index c24ec429f8a3..000000000000 --- a/script/tool/lib/src/common/pub_version_finder.dart +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright 2013 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. - -import 'dart:convert'; - -import 'package:http/http.dart' as http; -import 'package:pub_semver/pub_semver.dart'; - -/// Finding version of [package] that is published on pub. -class PubVersionFinder { - /// Constructor. - /// - /// Note: you should manually close the [httpClient] when done using the finder. - PubVersionFinder({this.pubHost = defaultPubHost, required this.httpClient}); - - /// The default pub host to use. - static const String defaultPubHost = 'https://pub.dev'; - - /// The pub host url, defaults to `https://pub.dev`. - final String pubHost; - - /// The http client. - /// - /// You should manually close this client when done using this finder. - final http.Client httpClient; - - /// Get the package version on pub. - Future getPackageVersion( - {required String packageName}) async { - assert(packageName.isNotEmpty); - final Uri pubHostUri = Uri.parse(pubHost); - final Uri url = pubHostUri.replace(path: '/packages/$packageName.json'); - final http.Response response = await httpClient.get(url); - - if (response.statusCode == 404) { - return PubVersionFinderResponse( - versions: [], - result: PubVersionFinderResult.noPackageFound, - httpResponse: response); - } else if (response.statusCode != 200) { - return PubVersionFinderResponse( - versions: [], - result: PubVersionFinderResult.fail, - httpResponse: response); - } - final Map responseBody = - json.decode(response.body) as Map; - final List versions = (responseBody['versions']! as List) - .cast() - .map( - (final String versionString) => Version.parse(versionString)) - .toList(); - - return PubVersionFinderResponse( - versions: versions, - result: PubVersionFinderResult.success, - httpResponse: response); - } -} - -/// Represents a response for [PubVersionFinder]. -class PubVersionFinderResponse { - /// Constructor. - PubVersionFinderResponse( - {required this.versions, - required this.result, - required this.httpResponse}) { - if (versions.isNotEmpty) { - versions.sort((Version a, Version b) { - // TODO(cyanglaz): Think about how to handle pre-release version with [Version.prioritize]. - // https://github.com/flutter/flutter/issues/82222 - return b.compareTo(a); - }); - } - } - - /// The versions found in [PubVersionFinder]. - /// - /// This is sorted by largest to smallest, so the first element in the list is the largest version. - /// Might be `null` if the [result] is not [PubVersionFinderResult.success]. - final List versions; - - /// The result of the version finder. - final PubVersionFinderResult result; - - /// The response object of the http request. - final http.Response httpResponse; -} - -/// An enum representing the result of [PubVersionFinder]. -enum PubVersionFinderResult { - /// The version finder successfully found a version. - success, - - /// The version finder failed to find a valid version. - /// - /// This might due to http connection errors or user errors. - fail, - - /// The version finder failed to locate the package. - /// - /// This indicates the package is new. - noPackageFound, -} diff --git a/script/tool/lib/src/common/xcode.dart b/script/tool/lib/src/common/xcode.dart deleted file mode 100644 index 83f681bcb492..000000000000 --- a/script/tool/lib/src/common/xcode.dart +++ /dev/null @@ -1,159 +0,0 @@ -// Copyright 2013 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. - -import 'dart:convert'; -import 'dart:io' as io; - -import 'package:file/file.dart'; - -import 'core.dart'; -import 'process_runner.dart'; - -const String _xcodeBuildCommand = 'xcodebuild'; -const String _xcRunCommand = 'xcrun'; - -/// A utility class for interacting with the installed version of Xcode. -class Xcode { - /// Creates an instance that runs commands with the given [processRunner]. - /// - /// If [log] is true, commands run by this instance will long various status - /// messages. - Xcode({ - this.processRunner = const ProcessRunner(), - this.log = false, - }); - - /// The [ProcessRunner] used to run commands. Overridable for testing. - final ProcessRunner processRunner; - - /// Whether or not to log when running commands. - final bool log; - - /// Runs an `xcodebuild` in [directory] with the given parameters. - Future runXcodeBuild( - Directory directory, { - List actions = const ['build'], - required String workspace, - required String scheme, - String? configuration, - List extraFlags = const [], - }) { - final List args = [ - _xcodeBuildCommand, - ...actions, - if (workspace != null) ...['-workspace', workspace], - if (scheme != null) ...['-scheme', scheme], - if (configuration != null) ...['-configuration', configuration], - ...extraFlags, - ]; - final String completeTestCommand = '$_xcRunCommand ${args.join(' ')}'; - if (log) { - print(completeTestCommand); - } - return processRunner.runAndStream(_xcRunCommand, args, - workingDir: directory); - } - - /// Returns true if [project], which should be an .xcodeproj directory, - /// contains a target called [target], false if it does not, and null if the - /// check fails (e.g., if [project] is not an Xcode project). - Future projectHasTarget(Directory project, String target) async { - final io.ProcessResult result = - await processRunner.run(_xcRunCommand, [ - _xcodeBuildCommand, - '-list', - '-json', - '-project', - project.path, - ]); - if (result.exitCode != 0) { - return null; - } - Map? projectInfo; - try { - projectInfo = (jsonDecode(result.stdout as String) - as Map)['project'] as Map?; - } on FormatException { - return null; - } - if (projectInfo == null) { - return null; - } - final List? targets = - (projectInfo['targets'] as List?)?.cast(); - return targets?.contains(target) ?? false; - } - - /// Returns the newest available simulator (highest OS version, with ties - /// broken in favor of newest device), if any. - Future findBestAvailableIphoneSimulator() async { - final List findSimulatorsArguments = [ - 'simctl', - 'list', - 'devices', - 'runtimes', - 'available', - '--json', - ]; - final String findSimulatorCompleteCommand = - '$_xcRunCommand ${findSimulatorsArguments.join(' ')}'; - if (log) { - print('Looking for available simulators...'); - print(findSimulatorCompleteCommand); - } - final io.ProcessResult findSimulatorsResult = - await processRunner.run(_xcRunCommand, findSimulatorsArguments); - if (findSimulatorsResult.exitCode != 0) { - if (log) { - printError( - 'Error occurred while running "$findSimulatorCompleteCommand":\n' - '${findSimulatorsResult.stderr}'); - } - return null; - } - final Map simulatorListJson = - jsonDecode(findSimulatorsResult.stdout as String) - as Map; - final List> runtimes = - (simulatorListJson['runtimes'] as List) - .cast>(); - final Map devices = - (simulatorListJson['devices'] as Map) - .cast(); - if (runtimes.isEmpty || devices.isEmpty) { - return null; - } - String? id; - // Looking for runtimes, trying to find one with highest OS version. - for (final Map rawRuntimeMap in runtimes.reversed) { - final Map runtimeMap = - rawRuntimeMap.cast(); - if ((runtimeMap['name'] as String?)?.contains('iOS') != true) { - continue; - } - final String? runtimeID = runtimeMap['identifier'] as String?; - if (runtimeID == null) { - continue; - } - final List>? devicesForRuntime = - (devices[runtimeID] as List?)?.cast>(); - if (devicesForRuntime == null || devicesForRuntime.isEmpty) { - continue; - } - // Looking for runtimes, trying to find latest version of device. - for (final Map rawDevice in devicesForRuntime.reversed) { - final Map device = rawDevice.cast(); - id = device['udid'] as String?; - if (id == null) { - continue; - } - if (log) { - print('device selected: $device'); - } - return id; - } - } - return null; - } -} diff --git a/script/tool/lib/src/create_all_packages_app_command.dart b/script/tool/lib/src/create_all_packages_app_command.dart deleted file mode 100644 index b56086ac1d0a..000000000000 --- a/script/tool/lib/src/create_all_packages_app_command.dart +++ /dev/null @@ -1,344 +0,0 @@ -// Copyright 2013 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. - -import 'dart:io' as io; - -import 'package:file/file.dart'; -import 'package:path/path.dart' as p; -import 'package:platform/platform.dart'; -import 'package:pub_semver/pub_semver.dart'; -import 'package:pubspec_parse/pubspec_parse.dart'; - -import 'common/core.dart'; -import 'common/package_command.dart'; -import 'common/process_runner.dart'; -import 'common/repository_package.dart'; - -const String _outputDirectoryFlag = 'output-dir'; - -const String _projectName = 'all_packages'; - -const int _exitUpdateMacosPodfileFailed = 3; -const int _exitUpdateMacosPbxprojFailed = 4; -const int _exitGenNativeBuildFilesFailed = 5; - -/// A command to create an application that builds all in a single application. -class CreateAllPackagesAppCommand extends PackageCommand { - /// Creates an instance of the builder command. - CreateAllPackagesAppCommand( - Directory packagesDir, { - ProcessRunner processRunner = const ProcessRunner(), - Directory? pluginsRoot, - Platform platform = const LocalPlatform(), - }) : super(packagesDir, processRunner: processRunner, platform: platform) { - final Directory defaultDir = - pluginsRoot ?? packagesDir.fileSystem.currentDirectory; - argParser.addOption(_outputDirectoryFlag, - defaultsTo: defaultDir.path, - help: - 'The path the directory to create the "$_projectName" project in.\n' - 'Defaults to the repository root.'); - } - - /// The location to create the synthesized app project. - Directory get _appDirectory => packagesDir.fileSystem - .directory(getStringArg(_outputDirectoryFlag)) - .childDirectory(_projectName); - - /// The synthesized app project. - RepositoryPackage get app => RepositoryPackage(_appDirectory); - - @override - String get description => - 'Generate Flutter app that includes all target packagas.'; - - @override - String get name => 'create-all-packages-app'; - - @override - Future run() async { - final int exitCode = await _createApp(); - if (exitCode != 0) { - throw ToolExit(exitCode); - } - - final Set excluded = getExcludedPackageNames(); - if (excluded.isNotEmpty) { - print('Exluding the following plugins from the combined build:'); - for (final String plugin in excluded) { - print(' $plugin'); - } - print(''); - } - - await _genPubspecWithAllPlugins(); - - // Run `flutter pub get` to generate all native build files. - // TODO(stuartmorgan): This hangs on Windows for some reason. Since it's - // currently not needed on Windows, skip it there, but we should investigate - // further and/or implement https://github.com/flutter/flutter/issues/93407, - // and remove the need for this conditional. - if (!platform.isWindows) { - if (!await _genNativeBuildFiles()) { - printError( - "Failed to generate native build files via 'flutter pub get'"); - throw ToolExit(_exitGenNativeBuildFilesFailed); - } - } - - await Future.wait(>[ - _updateAppGradle(), - _updateManifest(), - _updateMacosPbxproj(), - // This step requires the native file generation triggered by - // flutter pub get above, so can't currently be run on Windows. - if (!platform.isWindows) _updateMacosPodfile(), - ]); - } - - Future _createApp() async { - final io.ProcessResult result = io.Process.runSync( - flutterCommand, - [ - 'create', - '--template=app', - '--project-name=$_projectName', - '--android-language=java', - _appDirectory.path, - ], - ); - - print(result.stdout); - print(result.stderr); - return result.exitCode; - } - - Future _updateAppGradle() async { - final File gradleFile = app - .platformDirectory(FlutterPlatform.android) - .childDirectory('app') - .childFile('build.gradle'); - if (!gradleFile.existsSync()) { - throw ToolExit(64); - } - - final StringBuffer newGradle = StringBuffer(); - for (final String line in gradleFile.readAsLinesSync()) { - if (line.contains('minSdkVersion')) { - // minSdkVersion 20 is required by Google maps. - // minSdkVersion 19 is required by WebView. - newGradle.writeln('minSdkVersion 20'); - } else if (line.contains('compileSdkVersion')) { - // compileSdkVersion 33 is required by local_auth. - newGradle.writeln('compileSdkVersion 33'); - } else { - newGradle.writeln(line); - } - if (line.contains('defaultConfig {')) { - newGradle.writeln(' multiDexEnabled true'); - } else if (line.contains('dependencies {')) { - // Tests for https://github.com/flutter/flutter/issues/43383 - newGradle.writeln( - " implementation 'androidx.lifecycle:lifecycle-runtime:2.2.0-rc01'\n", - ); - } - } - gradleFile.writeAsStringSync(newGradle.toString()); - } - - Future _updateManifest() async { - final File manifestFile = app - .platformDirectory(FlutterPlatform.android) - .childDirectory('app') - .childDirectory('src') - .childDirectory('main') - .childFile('AndroidManifest.xml'); - if (!manifestFile.existsSync()) { - throw ToolExit(64); - } - - final StringBuffer newManifest = StringBuffer(); - for (final String line in manifestFile.readAsLinesSync()) { - if (line.contains('package="com.example.$_projectName"')) { - newManifest - ..writeln('package="com.example.$_projectName"') - ..writeln('xmlns:tools="http://schemas.android.com/tools">') - ..writeln() - ..writeln( - '', - ); - } else { - newManifest.writeln(line); - } - } - manifestFile.writeAsStringSync(newManifest.toString()); - } - - Future _genPubspecWithAllPlugins() async { - // Read the old pubspec file's Dart SDK version, in order to preserve it - // in the new file. The template sometimes relies on having opted in to - // specific language features via SDK version, so using a different one - // can cause compilation failures. - final Pubspec originalPubspec = app.parsePubspec(); - const String dartSdkKey = 'sdk'; - final VersionConstraint dartSdkConstraint = - originalPubspec.environment?[dartSdkKey] ?? - VersionConstraint.compatibleWith( - Version.parse('2.12.0'), - ); - - final Map pluginDeps = - await _getValidPathDependencies(); - final Pubspec pubspec = Pubspec( - _projectName, - description: 'Flutter app containing all 1st party plugins.', - version: Version.parse('1.0.0+1'), - environment: { - dartSdkKey: dartSdkConstraint, - }, - dependencies: { - 'flutter': SdkDependency('flutter'), - }..addAll(pluginDeps), - devDependencies: { - 'flutter_test': SdkDependency('flutter'), - }, - dependencyOverrides: pluginDeps, - ); - app.pubspecFile.writeAsStringSync(_pubspecToString(pubspec)); - } - - Future> _getValidPathDependencies() async { - final Map pathDependencies = - {}; - - await for (final PackageEnumerationEntry entry in getTargetPackages()) { - final RepositoryPackage package = entry.package; - final Directory pluginDirectory = package.directory; - final String pluginName = pluginDirectory.basename; - final Pubspec pubspec = package.parsePubspec(); - - if (pubspec.publishTo != 'none') { - pathDependencies[pluginName] = PathDependency(pluginDirectory.path); - } - } - return pathDependencies; - } - - String _pubspecToString(Pubspec pubspec) { - return ''' -### Generated file. Do not edit. Run `dart pub global run flutter_plugin_tools gen-pubspec` to update. -name: ${pubspec.name} -description: ${pubspec.description} -publish_to: none - -version: ${pubspec.version} - -environment:${_pubspecMapString(pubspec.environment!)} - -dependencies:${_pubspecMapString(pubspec.dependencies)} - -dependency_overrides:${_pubspecMapString(pubspec.dependencyOverrides)} - -dev_dependencies:${_pubspecMapString(pubspec.devDependencies)} -###'''; - } - - String _pubspecMapString(Map values) { - final StringBuffer buffer = StringBuffer(); - - for (final MapEntry entry in values.entries) { - buffer.writeln(); - final Object? entryValue = entry.value; - if (entryValue is VersionConstraint) { - String value = entryValue.toString(); - // Range constraints require quoting. - if (value.startsWith('>') || value.startsWith('<')) { - value = "'$value'"; - } - buffer.write(' ${entry.key}: $value'); - } else if (entryValue is SdkDependency) { - buffer.write(' ${entry.key}: \n sdk: ${entryValue.sdk}'); - } else if (entryValue is PathDependency) { - String depPath = entryValue.path; - if (path.style == p.Style.windows) { - // Posix-style path separators are preferred in pubspec.yaml (and - // using a consistent format makes unit testing simpler), so convert. - final List components = path.split(depPath); - final String firstComponent = components.first; - // path.split leaves a \ on drive components that isn't necessary, - // and confuses pub, so remove it. - if (firstComponent.endsWith(r':\')) { - components[0] = - firstComponent.substring(0, firstComponent.length - 1); - } - depPath = p.posix.joinAll(components); - } - buffer.write(' ${entry.key}: \n path: $depPath'); - } else { - throw UnimplementedError( - 'Not available for type: ${entryValue.runtimeType}', - ); - } - } - - return buffer.toString(); - } - - Future _genNativeBuildFiles() async { - final int exitCode = await processRunner.runAndStream( - flutterCommand, - ['pub', 'get'], - workingDir: _appDirectory, - ); - return exitCode == 0; - } - - Future _updateMacosPodfile() async { - /// Only change the macOS deployment target if the host platform is macOS. - /// The Podfile is not generated on other platforms. - if (!platform.isMacOS) { - return; - } - - final File podfileFile = - app.platformDirectory(FlutterPlatform.macos).childFile('Podfile'); - if (!podfileFile.existsSync()) { - printError("Can't find Podfile for macOS"); - throw ToolExit(_exitUpdateMacosPodfileFailed); - } - - final StringBuffer newPodfile = StringBuffer(); - for (final String line in podfileFile.readAsLinesSync()) { - if (line.contains('platform :osx')) { - // macOS 10.15 is required by in_app_purchase. - newPodfile.writeln("platform :osx, '10.15'"); - } else { - newPodfile.writeln(line); - } - } - podfileFile.writeAsStringSync(newPodfile.toString()); - } - - Future _updateMacosPbxproj() async { - final File pbxprojFile = app - .platformDirectory(FlutterPlatform.macos) - .childDirectory('Runner.xcodeproj') - .childFile('project.pbxproj'); - if (!pbxprojFile.existsSync()) { - printError("Can't find project.pbxproj for macOS"); - throw ToolExit(_exitUpdateMacosPbxprojFailed); - } - - final StringBuffer newPbxproj = StringBuffer(); - for (final String line in pbxprojFile.readAsLinesSync()) { - if (line.contains('MACOSX_DEPLOYMENT_TARGET')) { - // macOS 10.15 is required by in_app_purchase. - newPbxproj.writeln(' MACOSX_DEPLOYMENT_TARGET = 10.15;'); - } else { - newPbxproj.writeln(line); - } - } - pbxprojFile.writeAsStringSync(newPbxproj.toString()); - } -} diff --git a/script/tool/lib/src/custom_test_command.dart b/script/tool/lib/src/custom_test_command.dart deleted file mode 100644 index 0ef6e602c070..000000000000 --- a/script/tool/lib/src/custom_test_command.dart +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright 2013 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. - -import 'package:file/file.dart'; -import 'package:platform/platform.dart'; - -import 'common/package_looping_command.dart'; -import 'common/process_runner.dart'; -import 'common/repository_package.dart'; - -const String _scriptName = 'run_tests.dart'; -const String _legacyScriptName = 'run_tests.sh'; - -/// A command to run custom, package-local tests on packages. -/// -/// This is an escape hatch for adding tests that this tooling doesn't support. -/// It should be used sparingly; prefer instead to add functionality to this -/// tooling to eliminate the need for bespoke tests. -class CustomTestCommand extends PackageLoopingCommand { - /// Creates a custom test command instance. - CustomTestCommand( - Directory packagesDir, { - ProcessRunner processRunner = const ProcessRunner(), - Platform platform = const LocalPlatform(), - }) : super(packagesDir, processRunner: processRunner, platform: platform); - - @override - final String name = 'custom-test'; - - @override - final String description = 'Runs package-specific custom tests defined in ' - "a package's tool/$_scriptName file.\n\n" - 'This command requires "dart" to be in your path.'; - - @override - Future runForPackage(RepositoryPackage package) async { - final File script = - package.directory.childDirectory('tool').childFile(_scriptName); - final File legacyScript = package.directory.childFile(_legacyScriptName); - String? customSkipReason; - bool ranTests = false; - - // Run the custom Dart script if presest. - if (script.existsSync()) { - // Ensure that dependencies are available. - final int pubGetExitCode = await processRunner.runAndStream( - 'dart', ['pub', 'get'], - workingDir: package.directory); - if (pubGetExitCode != 0) { - return PackageResult.fail( - ['Unable to get script dependencies']); - } - - final int testExitCode = await processRunner.runAndStream( - 'dart', ['run', 'tool/$_scriptName'], - workingDir: package.directory); - if (testExitCode != 0) { - return PackageResult.fail(); - } - ranTests = true; - } - - // Run the legacy script if present. - if (legacyScript.existsSync()) { - if (platform.isWindows) { - customSkipReason = '$_legacyScriptName is not supported on Windows. ' - 'Please migrate to $_scriptName.'; - } else { - final int exitCode = await processRunner.runAndStream( - legacyScript.path, [], - workingDir: package.directory); - if (exitCode != 0) { - return PackageResult.fail(); - } - ranTests = true; - } - } - - if (!ranTests) { - return PackageResult.skip(customSkipReason ?? 'No custom tests'); - } - - return PackageResult.success(); - } -} diff --git a/script/tool/lib/src/dependabot_check_command.dart b/script/tool/lib/src/dependabot_check_command.dart deleted file mode 100644 index 77b44e11b59e..000000000000 --- a/script/tool/lib/src/dependabot_check_command.dart +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright 2013 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. - -import 'dart:async'; - -import 'package:file/file.dart'; -import 'package:git/git.dart'; -import 'package:yaml/yaml.dart'; - -import 'common/core.dart'; -import 'common/package_looping_command.dart'; -import 'common/repository_package.dart'; - -/// A command to verify Dependabot configuration coverage of packages. -class DependabotCheckCommand extends PackageLoopingCommand { - /// Creates Dependabot check command instance. - DependabotCheckCommand(Directory packagesDir, {GitDir? gitDir}) - : super(packagesDir, gitDir: gitDir) { - argParser.addOption(_configPathFlag, - help: 'Path to the Dependabot configuration file', - defaultsTo: '.github/dependabot.yml'); - } - - static const String _configPathFlag = 'config'; - - late Directory _repoRoot; - - // The set of directories covered by "gradle" entries in the config. - Set _gradleDirs = const {}; - - @override - final String name = 'dependabot-check'; - - @override - final String description = - 'Checks that all packages have Dependabot coverage.'; - - @override - final PackageLoopingType packageLoopingType = - PackageLoopingType.includeAllSubpackages; - - @override - final bool hasLongOutput = false; - - @override - Future initializeRun() async { - _repoRoot = packagesDir.fileSystem.directory((await gitDir).path); - - final YamlMap config = loadYaml(_repoRoot - .childFile(getStringArg(_configPathFlag)) - .readAsStringSync()) as YamlMap; - final dynamic entries = config['updates']; - if (entries is! YamlList) { - return; - } - - const String typeKey = 'package-ecosystem'; - const String dirKey = 'directory'; - _gradleDirs = entries - .cast() - .where((YamlMap entry) => entry[typeKey] == 'gradle') - .map((YamlMap entry) => entry[dirKey] as String) - .toSet(); - } - - @override - Future runForPackage(RepositoryPackage package) async { - bool skipped = true; - final List errors = []; - - final RunState gradleState = _validateDependabotGradleCoverage(package); - skipped = skipped && gradleState == RunState.skipped; - if (gradleState == RunState.failed) { - printError('${indentation}Missing Gradle coverage.'); - errors.add('Missing Gradle coverage'); - } - - // TODO(stuartmorgan): Add other ecosystem checks here as more are enabled. - - if (skipped) { - return PackageResult.skip('No supported package ecosystems'); - } - return errors.isEmpty - ? PackageResult.success() - : PackageResult.fail(errors); - } - - /// Returns the state for the Dependabot coverage of the Gradle ecosystem for - /// [package]: - /// - succeeded if it includes gradle and is covered. - /// - failed if it includes gradle and is not covered. - /// - skipped if it doesn't include gradle. - RunState _validateDependabotGradleCoverage(RepositoryPackage package) { - final Directory androidDir = - package.platformDirectory(FlutterPlatform.android); - final Directory appDir = androidDir.childDirectory('app'); - if (appDir.existsSync()) { - // It's an app, so only check for the app directory to be covered. - final String dependabotPath = - '/${getRelativePosixPath(appDir, from: _repoRoot)}'; - return _gradleDirs.contains(dependabotPath) - ? RunState.succeeded - : RunState.failed; - } else if (androidDir.existsSync()) { - // It's a library, so only check for the android directory to be covered. - final String dependabotPath = - '/${getRelativePosixPath(androidDir, from: _repoRoot)}'; - return _gradleDirs.contains(dependabotPath) - ? RunState.succeeded - : RunState.failed; - } - return RunState.skipped; - } -} diff --git a/script/tool/lib/src/drive_examples_command.dart b/script/tool/lib/src/drive_examples_command.dart deleted file mode 100644 index e8fb11b5f289..000000000000 --- a/script/tool/lib/src/drive_examples_command.dart +++ /dev/null @@ -1,380 +0,0 @@ -// Copyright 2013 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. - -import 'dart:convert'; -import 'dart:io'; - -import 'package:file/file.dart'; -import 'package:platform/platform.dart'; - -import 'common/core.dart'; -import 'common/package_looping_command.dart'; -import 'common/plugin_utils.dart'; -import 'common/process_runner.dart'; -import 'common/repository_package.dart'; - -const int _exitNoPlatformFlags = 2; -const int _exitNoAvailableDevice = 3; - -/// A command to run the example applications for packages via Flutter driver. -class DriveExamplesCommand extends PackageLoopingCommand { - /// Creates an instance of the drive command. - DriveExamplesCommand( - Directory packagesDir, { - ProcessRunner processRunner = const ProcessRunner(), - Platform platform = const LocalPlatform(), - }) : super(packagesDir, processRunner: processRunner, platform: platform) { - argParser.addFlag(platformAndroid, - help: 'Runs the Android implementation of the examples'); - argParser.addFlag(platformIOS, - help: 'Runs the iOS implementation of the examples'); - argParser.addFlag(platformLinux, - help: 'Runs the Linux implementation of the examples'); - argParser.addFlag(platformMacOS, - help: 'Runs the macOS implementation of the examples'); - argParser.addFlag(platformWeb, - help: 'Runs the web implementation of the examples'); - argParser.addFlag(platformWindows, - help: 'Runs the Windows implementation of the examples'); - argParser.addOption( - kEnableExperiment, - defaultsTo: '', - help: - 'Runs the driver tests in Dart VM with the given experiments enabled.', - ); - } - - @override - final String name = 'drive-examples'; - - @override - final String description = 'Runs driver tests for package example apps.\n\n' - 'For each *_test.dart in test_driver/ it drives an application with ' - 'either the corresponding test in test_driver (for example, ' - 'test_driver/app_test.dart would match test_driver/app.dart), or the ' - '*_test.dart files in integration_test/.\n\n' - 'This command requires "flutter" to be in your path.'; - - Map> _targetDeviceFlags = const >{}; - - @override - Future initializeRun() async { - final List platformSwitches = [ - platformAndroid, - platformIOS, - platformLinux, - platformMacOS, - platformWeb, - platformWindows, - ]; - final int platformCount = platformSwitches - .where((String platform) => getBoolArg(platform)) - .length; - // The flutter tool currently doesn't accept multiple device arguments: - // https://github.com/flutter/flutter/issues/35733 - // If that is implemented, this check can be relaxed. - if (platformCount != 1) { - printError( - 'Exactly one of ${platformSwitches.map((String platform) => '--$platform').join(', ')} ' - 'must be specified.'); - throw ToolExit(_exitNoPlatformFlags); - } - - String? androidDevice; - if (getBoolArg(platformAndroid)) { - final List devices = await _getDevicesForPlatform('android'); - if (devices.isEmpty) { - printError('No Android devices available'); - throw ToolExit(_exitNoAvailableDevice); - } - androidDevice = devices.first; - } - - String? iOSDevice; - if (getBoolArg(platformIOS)) { - final List devices = await _getDevicesForPlatform('ios'); - if (devices.isEmpty) { - printError('No iOS devices available'); - throw ToolExit(_exitNoAvailableDevice); - } - iOSDevice = devices.first; - } - - _targetDeviceFlags = >{ - if (getBoolArg(platformAndroid)) - platformAndroid: ['-d', androidDevice!], - if (getBoolArg(platformIOS)) platformIOS: ['-d', iOSDevice!], - if (getBoolArg(platformLinux)) platformLinux: ['-d', 'linux'], - if (getBoolArg(platformMacOS)) platformMacOS: ['-d', 'macos'], - if (getBoolArg(platformWeb)) - platformWeb: [ - '-d', - 'web-server', - '--web-port=7357', - '--browser-name=chrome', - if (platform.environment.containsKey('CHROME_EXECUTABLE')) - '--chrome-binary=${platform.environment['CHROME_EXECUTABLE']}', - ], - if (getBoolArg(platformWindows)) - platformWindows: ['-d', 'windows'], - }; - } - - @override - Future runForPackage(RepositoryPackage package) async { - final bool isPlugin = isFlutterPlugin(package); - - if (package.isPlatformInterface && package.getExamples().isEmpty) { - // Platform interface packages generally aren't intended to have - // examples, and don't need integration tests, so skip rather than fail. - return PackageResult.skip( - 'Platform interfaces are not expected to have integration tests.'); - } - - // For plugin packages, skip if the plugin itself doesn't support any - // requested platform(s). - if (isPlugin) { - final Iterable requestedPlatforms = _targetDeviceFlags.keys; - final Iterable unsupportedPlatforms = requestedPlatforms.where( - (String platform) => !pluginSupportsPlatform(platform, package)); - for (final String platform in unsupportedPlatforms) { - print('Skipping unsupported platform $platform...'); - } - if (unsupportedPlatforms.length == requestedPlatforms.length) { - return PackageResult.skip( - '${package.displayName} does not support any requested platform.'); - } - } - - int examplesFound = 0; - int supportedExamplesFound = 0; - bool testsRan = false; - final List errors = []; - for (final RepositoryPackage example in package.getExamples()) { - ++examplesFound; - final String exampleName = - getRelativePosixPath(example.directory, from: packagesDir); - - // Skip examples that don't support any requested platform(s). - final List deviceFlags = _deviceFlagsForExample(example); - if (deviceFlags.isEmpty) { - print( - 'Skipping $exampleName; does not support any requested platforms.'); - continue; - } - ++supportedExamplesFound; - - final List drivers = await _getDrivers(example); - if (drivers.isEmpty) { - print('No driver tests found for $exampleName'); - continue; - } - - for (final File driver in drivers) { - final List testTargets = []; - - // Try to find a matching app to drive without the _test.dart - // TODO(stuartmorgan): Migrate all remaining uses of this legacy - // approach (currently only video_player) and remove support for it: - // https://github.com/flutter/flutter/issues/85224. - final File? legacyTestFile = _getLegacyTestFileForTestDriver(driver); - if (legacyTestFile != null) { - testTargets.add(legacyTestFile); - } else { - for (final File testFile in await _getIntegrationTests(example)) { - // Check files for known problematic patterns. - final bool passesValidation = _validateIntegrationTest(testFile); - if (!passesValidation) { - // Report the issue, but continue with the test as the validation - // errors don't prevent running. - errors.add('${testFile.basename} failed validation'); - } - testTargets.add(testFile); - } - } - - if (testTargets.isEmpty) { - final String driverRelativePath = - getRelativePosixPath(driver, from: package.directory); - printError( - 'Found $driverRelativePath, but no integration_test/*_test.dart files.'); - errors.add('No test files for $driverRelativePath'); - continue; - } - - testsRan = true; - final List failingTargets = await _driveTests( - example, driver, testTargets, - deviceFlags: deviceFlags); - for (final File failingTarget in failingTargets) { - errors.add( - getRelativePosixPath(failingTarget, from: package.directory)); - } - } - } - if (!testsRan) { - // It is an error for a plugin not to have integration tests, because that - // is the only way to test the method channel communication. - if (isPlugin) { - printError( - 'No driver tests were run ($examplesFound example(s) found).'); - errors.add('No tests ran (use --exclude if this is intentional).'); - } else { - return PackageResult.skip(supportedExamplesFound == 0 - ? 'No example supports requested platform(s).' - : 'No example is configured for driver tests.'); - } - } - return errors.isEmpty - ? PackageResult.success() - : PackageResult.fail(errors); - } - - /// Returns the device flags for the intersection of the requested platforms - /// and the platforms supported by [example]. - List _deviceFlagsForExample(RepositoryPackage example) { - final List deviceFlags = []; - for (final MapEntry> entry - in _targetDeviceFlags.entries) { - final String platform = entry.key; - if (example.directory.childDirectory(platform).existsSync()) { - deviceFlags.addAll(entry.value); - } else { - final String exampleName = - getRelativePosixPath(example.directory, from: packagesDir); - print('Skipping unsupported platform $platform for $exampleName'); - } - } - return deviceFlags; - } - - Future> _getDevicesForPlatform(String platform) async { - final List deviceIds = []; - - final ProcessResult result = await processRunner.run( - flutterCommand, ['devices', '--machine'], - stdoutEncoding: utf8); - if (result.exitCode != 0) { - return deviceIds; - } - - String output = result.stdout as String; - // --machine doesn't currently prevent the tool from printing banners; - // see https://github.com/flutter/flutter/issues/86055. This workaround - // can be removed once that is fixed. - output = output.substring(output.indexOf('[')); - - final List> devices = - (jsonDecode(output) as List).cast>(); - for (final Map deviceInfo in devices) { - final String targetPlatform = - (deviceInfo['targetPlatform'] as String?) ?? ''; - if (targetPlatform.startsWith(platform)) { - final String? deviceId = deviceInfo['id'] as String?; - if (deviceId != null) { - deviceIds.add(deviceId); - } - } - } - return deviceIds; - } - - Future> _getDrivers(RepositoryPackage example) async { - final List drivers = []; - - final Directory driverDir = example.directory.childDirectory('test_driver'); - if (driverDir.existsSync()) { - await for (final FileSystemEntity driver in driverDir.list()) { - if (driver is File && driver.basename.endsWith('_test.dart')) { - drivers.add(driver); - } - } - } - return drivers; - } - - File? _getLegacyTestFileForTestDriver(File testDriver) { - final String testName = testDriver.basename.replaceAll( - RegExp(r'_test.dart$'), - '.dart', - ); - final File testFile = testDriver.parent.childFile(testName); - - return testFile.existsSync() ? testFile : null; - } - - Future> _getIntegrationTests(RepositoryPackage example) async { - final List tests = []; - final Directory integrationTestDir = - example.directory.childDirectory('integration_test'); - - if (integrationTestDir.existsSync()) { - await for (final FileSystemEntity file in integrationTestDir.list()) { - if (file is File && file.basename.endsWith('_test.dart')) { - tests.add(file); - } - } - } - return tests; - } - - /// Checks [testFile] for known bad patterns in integration tests, logging - /// any issues. - /// - /// Returns true if the file passes validation without issues. - bool _validateIntegrationTest(File testFile) { - final List lines = testFile.readAsLinesSync(); - - final RegExp badTestPattern = RegExp(r'\s*test\('); - if (lines.any((String line) => line.startsWith(badTestPattern))) { - final String filename = testFile.basename; - printError( - '$filename uses "test", which will not report failures correctly. ' - 'Use testWidgets instead.'); - return false; - } - - return true; - } - - /// For each file in [targets], uses - /// `flutter drive --driver [driver] --target ` - /// to drive [example], returning a list of any failing test targets. - /// - /// [deviceFlags] should contain the flags to run the test on a specific - /// target device (plus any supporting device-specific flags). E.g.: - /// - `['-d', 'macos']` for driving for macOS. - /// - `['-d', 'web-server', '--web-port=', '--browser-name=]` - /// for web - Future> _driveTests( - RepositoryPackage example, - File driver, - List targets, { - required List deviceFlags, - }) async { - final List failures = []; - - final String enableExperiment = getStringArg(kEnableExperiment); - - for (final File target in targets) { - final int exitCode = await processRunner.runAndStream( - flutterCommand, - [ - 'drive', - ...deviceFlags, - if (enableExperiment.isNotEmpty) - '--enable-experiment=$enableExperiment', - '--driver', - getRelativePosixPath(driver, from: example.directory), - '--target', - getRelativePosixPath(target, from: example.directory), - ], - workingDir: example.directory); - if (exitCode != 0) { - failures.add(target); - } - } - return failures; - } -} diff --git a/script/tool/lib/src/federation_safety_check_command.dart b/script/tool/lib/src/federation_safety_check_command.dart deleted file mode 100644 index 93a832eb0e29..000000000000 --- a/script/tool/lib/src/federation_safety_check_command.dart +++ /dev/null @@ -1,199 +0,0 @@ -// Copyright 2013 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. - -import 'package:file/file.dart'; -import 'package:git/git.dart'; -import 'package:path/path.dart' as p; -import 'package:platform/platform.dart'; -import 'package:pub_semver/pub_semver.dart'; - -import 'common/core.dart'; -import 'common/file_utils.dart'; -import 'common/git_version_finder.dart'; -import 'common/package_looping_command.dart'; -import 'common/plugin_utils.dart'; -import 'common/process_runner.dart'; -import 'common/repository_package.dart'; - -/// A command to check that PRs don't violate repository best practices that -/// have been established to avoid breakages that building and testing won't -/// catch. -class FederationSafetyCheckCommand extends PackageLoopingCommand { - /// Creates an instance of the safety check command. - FederationSafetyCheckCommand( - Directory packagesDir, { - ProcessRunner processRunner = const ProcessRunner(), - Platform platform = const LocalPlatform(), - GitDir? gitDir, - }) : super( - packagesDir, - processRunner: processRunner, - platform: platform, - gitDir: gitDir, - ); - - // A map of package name (as defined by the directory name of the package) - // to a list of changed Dart files in that package, as Posix paths relative to - // the package root. - // - // This only considers top-level packages, not subpackages such as example/. - final Map> _changedDartFiles = >{}; - - // The set of *_platform_interface packages that will have public code changes - // published. - final Set _modifiedAndPublishedPlatformInterfacePackages = {}; - - // The set of conceptual plugins (not packages) that have changes. - final Set _changedPlugins = {}; - - static const String _platformInterfaceSuffix = '_platform_interface'; - - @override - final String name = 'federation-safety-check'; - - @override - final String description = - 'Checks that the change does not violate repository rules around changes ' - 'to federated plugin packages.'; - - @override - bool get hasLongOutput => false; - - @override - Future initializeRun() async { - final GitVersionFinder gitVersionFinder = await retrieveVersionFinder(); - final String baseSha = await gitVersionFinder.getBaseSha(); - print('Validating changes relative to "$baseSha"\n'); - for (final String path in await gitVersionFinder.getChangedFiles()) { - // Git output always uses Posix paths. - final List allComponents = p.posix.split(path); - final int packageIndex = allComponents.indexOf('packages'); - if (packageIndex == -1) { - continue; - } - final List relativeComponents = - allComponents.sublist(packageIndex + 1); - // The package name is either the directory directly under packages/, or - // the directory under that in the case of a federated plugin. - String packageName = relativeComponents.removeAt(0); - // Count the top-level plugin as changed. - _changedPlugins.add(packageName); - if (relativeComponents[0] == packageName || - (relativeComponents.length > 1 && - relativeComponents[0].startsWith('${packageName}_'))) { - packageName = relativeComponents.removeAt(0); - } - - if (relativeComponents.last.endsWith('.dart')) { - _changedDartFiles[packageName] ??= []; - _changedDartFiles[packageName]! - .add(p.posix.joinAll(relativeComponents)); - } - - if (packageName.endsWith(_platformInterfaceSuffix) && - relativeComponents.first == 'pubspec.yaml' && - await _packageWillBePublished(path)) { - _modifiedAndPublishedPlatformInterfacePackages.add(packageName); - } - } - } - - @override - Future runForPackage(RepositoryPackage package) async { - if (!isFlutterPlugin(package)) { - return PackageResult.skip('Not a plugin.'); - } - - if (!package.isFederated) { - return PackageResult.skip('Not a federated plugin.'); - } - - if (package.isPlatformInterface) { - // As the leaf nodes in the graph, a published package interface change is - // assumed to be correct, and other changes are validated against that. - return PackageResult.skip( - 'Platform interface changes are not validated.'); - } - - // Uses basename to match _changedPackageFiles. - final String basePackageName = package.directory.parent.basename; - final String platformInterfacePackageName = - '$basePackageName$_platformInterfaceSuffix'; - final List changedPlatformInterfaceFiles = - _changedDartFiles[platformInterfacePackageName] ?? []; - - if (!_modifiedAndPublishedPlatformInterfacePackages - .contains(platformInterfacePackageName)) { - print('No published changes for $platformInterfacePackageName.'); - return PackageResult.success(); - } - - if (!changedPlatformInterfaceFiles - .any((String path) => path.startsWith('lib/'))) { - print('No public code changes for $platformInterfacePackageName.'); - return PackageResult.success(); - } - - final List changedPackageFiles = - _changedDartFiles[package.directory.basename] ?? []; - if (changedPackageFiles.isEmpty) { - print('No Dart changes.'); - return PackageResult.success(); - } - - // If the change would be flagged, but it appears to be a mass change - // rather than a plugin-specific change, allow it with a warning. - // - // This is a tradeoff between safety and convenience; forcing mass changes - // to be split apart is not ideal, and the assumption is that reviewers are - // unlikely to accidentally approve a PR that is supposed to be changing a - // single plugin, but touches other plugins (vs accidentally approving a - // PR that changes multiple parts of a single plugin, which is a relatively - // easy mistake to make). - // - // 3 is chosen to minimize the chances of accidentally letting something - // through (vs 2, which could be a single-plugin change with one stray - // change to another file accidentally included), while not setting too - // high a bar for detecting mass changes. This can be tuned if there are - // issues with false positives or false negatives. - const int massChangePluginThreshold = 3; - if (_changedPlugins.length >= massChangePluginThreshold) { - logWarning('Ignoring potentially dangerous change, as this appears ' - 'to be a mass change.'); - return PackageResult.success(); - } - - printError('Dart changes are not allowed to other packages in ' - '$basePackageName in the same PR as changes to public Dart code in ' - '$platformInterfacePackageName, as this can cause accidental breaking ' - 'changes to be missed by automated checks. Please split the changes to ' - 'these two packages into separate PRs.\n\n' - 'If you believe that this is a false positive, please file a bug.'); - return PackageResult.fail( - ['$platformInterfacePackageName changed.']); - } - - Future _packageWillBePublished( - String pubspecRepoRelativePosixPath) async { - final File pubspecFile = childFileWithSubcomponents( - packagesDir.parent, p.posix.split(pubspecRepoRelativePosixPath)); - if (!pubspecFile.existsSync()) { - // If the package was deleted, nothing will be published. - return false; - } - final Pubspec pubspec = Pubspec.parse(pubspecFile.readAsStringSync()); - if (pubspec.publishTo == 'none') { - return false; - } - - final GitVersionFinder gitVersionFinder = await retrieveVersionFinder(); - final Version? previousVersion = - await gitVersionFinder.getPackageVersion(pubspecRepoRelativePosixPath); - if (previousVersion == null) { - // The plugin is new, so it will be published. - return true; - } - return pubspec.version != previousVersion; - } -} diff --git a/script/tool/lib/src/firebase_test_lab_command.dart b/script/tool/lib/src/firebase_test_lab_command.dart deleted file mode 100644 index a11284411908..000000000000 --- a/script/tool/lib/src/firebase_test_lab_command.dart +++ /dev/null @@ -1,358 +0,0 @@ -// Copyright 2013 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. - -import 'dart:async'; -import 'dart:io' as io; - -import 'package:file/file.dart'; -import 'package:platform/platform.dart'; -import 'package:uuid/uuid.dart'; - -import 'common/core.dart'; -import 'common/gradle.dart'; -import 'common/package_looping_command.dart'; -import 'common/process_runner.dart'; -import 'common/repository_package.dart'; - -const int _exitGcloudAuthFailed = 2; - -/// A command to run tests via Firebase test lab. -class FirebaseTestLabCommand extends PackageLoopingCommand { - /// Creates an instance of the test runner command. - FirebaseTestLabCommand( - Directory packagesDir, { - ProcessRunner processRunner = const ProcessRunner(), - Platform platform = const LocalPlatform(), - }) : super(packagesDir, processRunner: processRunner, platform: platform) { - argParser.addOption( - 'project', - defaultsTo: 'flutter-cirrus', - help: 'The Firebase project name.', - ); - final String? homeDir = io.Platform.environment['HOME']; - argParser.addOption('service-key', - defaultsTo: homeDir == null - ? null - : path.join(homeDir, 'gcloud-service-key.json'), - help: 'The path to the service key for gcloud authentication.\n' - r'If not provided, \$HOME/gcloud-service-key.json will be ' - r'assumed if $HOME is set.'); - argParser.addOption('test-run-id', - defaultsTo: const Uuid().v4(), - help: - 'Optional string to append to the results path, to avoid conflicts. ' - 'Randomly chosen on each invocation if none is provided. ' - 'The default shown here is just an example.'); - argParser.addOption('build-id', - defaultsTo: - io.Platform.environment['CIRRUS_BUILD_ID'] ?? 'unknown_build', - help: - 'Optional string to append to the results path, to avoid conflicts. ' - r'Defaults to $CIRRUS_BUILD_ID if that is set.'); - argParser.addMultiOption('device', - splitCommas: false, - defaultsTo: [ - 'model=walleye,version=26', - 'model=redfin,version=30' - ], - help: - 'Device model(s) to test. See https://cloud.google.com/sdk/gcloud/reference/firebase/test/android/run for more info'); - argParser.addOption('results-bucket', - defaultsTo: 'gs://flutter_cirrus_testlab'); - argParser.addOption( - kEnableExperiment, - defaultsTo: '', - help: 'Enables the given Dart SDK experiments.', - ); - } - - @override - final String name = 'firebase-test-lab'; - - @override - final String description = 'Runs the instrumentation tests of the example ' - 'apps on Firebase Test Lab.\n\n' - 'Runs tests in test_instrumentation folder using the ' - 'instrumentation_test package.'; - - bool _firebaseProjectConfigured = false; - - Future _configureFirebaseProject() async { - if (_firebaseProjectConfigured) { - return; - } - - final String serviceKey = getStringArg('service-key'); - if (serviceKey.isEmpty) { - print('No --service-key provided; skipping gcloud authorization'); - } else { - final io.ProcessResult result = await processRunner.run( - 'gcloud', - [ - 'auth', - 'activate-service-account', - '--key-file=$serviceKey', - ], - logOnError: true, - ); - if (result.exitCode != 0) { - printError('Unable to activate gcloud account.'); - throw ToolExit(_exitGcloudAuthFailed); - } - final int exitCode = await processRunner.runAndStream('gcloud', [ - 'config', - 'set', - 'project', - getStringArg('project'), - ]); - print(''); - if (exitCode == 0) { - print('Firebase project configured.'); - } else { - logWarning( - 'Warning: gcloud config set returned a non-zero exit code. Continuing anyway.'); - } - } - _firebaseProjectConfigured = true; - } - - @override - Future runForPackage(RepositoryPackage package) async { - final List results = []; - for (final RepositoryPackage example in package.getExamples()) { - results.add(await _runForExample(example, package: package)); - } - - // If all results skipped, report skip overall. - if (results - .every((PackageResult result) => result.state == RunState.skipped)) { - return PackageResult.skip('No examples support Android.'); - } - // Otherwise, report failure if there were any failures. - final List allErrors = results - .map((PackageResult result) => - result.state == RunState.failed ? result.details : []) - .expand((List list) => list) - .toList(); - return allErrors.isEmpty - ? PackageResult.success() - : PackageResult.fail(allErrors); - } - - /// Runs the test for the given example of [package]. - Future _runForExample( - RepositoryPackage example, { - required RepositoryPackage package, - }) async { - final Directory androidDirectory = - example.platformDirectory(FlutterPlatform.android); - if (!androidDirectory.existsSync()) { - return PackageResult.skip( - '${example.displayName} does not support Android.'); - } - - final Directory uiTestDirectory = androidDirectory - .childDirectory('app') - .childDirectory('src') - .childDirectory('androidTest'); - if (!uiTestDirectory.existsSync()) { - printError('No androidTest directory found.'); - return PackageResult.fail( - ['No tests ran (use --exclude if this is intentional).']); - } - - // Ensure that the Dart integration tests will be run, not just native UI - // tests. - if (!await _testsContainDartIntegrationTestRunner(uiTestDirectory)) { - printError('No integration_test runner found. ' - 'See the integration_test package README for setup instructions.'); - return PackageResult.fail(['No integration_test runner.']); - } - - // Ensures that gradle wrapper exists - final GradleProject project = GradleProject(example, - processRunner: processRunner, platform: platform); - if (!await _ensureGradleWrapperExists(project)) { - return PackageResult.fail(['Unable to build example apk']); - } - - await _configureFirebaseProject(); - - if (!await _runGradle(project, 'app:assembleAndroidTest')) { - return PackageResult.fail(['Unable to assemble androidTest']); - } - - final List errors = []; - - // Used within the loop to ensure a unique GCS output location for each - // test file's run. - int resultsCounter = 0; - for (final File test in _findIntegrationTestFiles(example)) { - final String testName = - getRelativePosixPath(test, from: package.directory); - print('Testing $testName...'); - if (!await _runGradle(project, 'app:assembleDebug', testFile: test)) { - printError('Could not build $testName'); - errors.add('$testName failed to build'); - continue; - } - final String buildId = getStringArg('build-id'); - final String testRunId = getStringArg('test-run-id'); - final String resultsDir = - 'plugins_android_test/${package.displayName}/$buildId/$testRunId/' - '${example.directory.basename}/${resultsCounter++}/'; - - // Automatically retry failures; there is significant flake with these - // tests whose cause isn't yet understood, and having to re-run the - // entire shard for a flake in any one test is extremely slow. This should - // be removed once the root cause of the flake is understood. - // See https://github.com/flutter/flutter/issues/95063 - const int maxRetries = 2; - bool passing = false; - for (int i = 1; i <= maxRetries && !passing; ++i) { - if (i > 1) { - logWarning('$testName failed on attempt ${i - 1}. Retrying...'); - } - passing = await _runFirebaseTest(example, test, resultsDir: resultsDir); - } - if (!passing) { - printError('Test failure for $testName after $maxRetries attempts'); - errors.add('$testName failed tests'); - } - } - - if (errors.isEmpty && resultsCounter == 0) { - printError('No integration tests were run.'); - errors.add('No tests ran (use --exclude if this is intentional).'); - } - - return errors.isEmpty - ? PackageResult.success() - : PackageResult.fail(errors); - } - - /// Checks that Gradle has been configured for [project], and if not runs a - /// Flutter build to generate it. - /// - /// Returns true if either gradlew was already present, or the build succeeds. - Future _ensureGradleWrapperExists(GradleProject project) async { - if (!project.isConfigured()) { - print('Running flutter build apk...'); - final String experiment = getStringArg(kEnableExperiment); - final int exitCode = await processRunner.runAndStream( - flutterCommand, - [ - 'build', - 'apk', - if (experiment.isNotEmpty) '--enable-experiment=$experiment', - ], - workingDir: project.androidDirectory); - - if (exitCode != 0) { - return false; - } - } - return true; - } - - /// Runs [test] from [example] as a Firebase Test Lab test, returning true if - /// the test passed. - /// - /// [resultsDir] should be a unique-to-the-test-run directory to store the - /// results on the server. - Future _runFirebaseTest( - RepositoryPackage example, - File test, { - required String resultsDir, - }) async { - final List args = [ - 'firebase', - 'test', - 'android', - 'run', - '--type', - 'instrumentation', - '--app', - 'build/app/outputs/apk/debug/app-debug.apk', - '--test', - 'build/app/outputs/apk/androidTest/debug/app-debug-androidTest.apk', - '--timeout', - '7m', - '--results-bucket=${getStringArg('results-bucket')}', - '--results-dir=$resultsDir', - for (final String device in getStringListArg('device')) ...[ - '--device', - device - ], - ]; - final int exitCode = await processRunner.runAndStream('gcloud', args, - workingDir: example.directory); - - return exitCode == 0; - } - - /// Builds [target] using Gradle in the given [project]. Assumes Gradle is - /// already configured. - /// - /// [testFile] optionally does the Flutter build with the given test file as - /// the build target. - /// - /// Returns true if the command succeeds. - Future _runGradle( - GradleProject project, - String target, { - File? testFile, - }) async { - final String experiment = getStringArg(kEnableExperiment); - final String? extraOptions = experiment.isNotEmpty - ? Uri.encodeComponent('--enable-experiment=$experiment') - : null; - - final int exitCode = await project.runCommand( - target, - arguments: [ - '-Pverbose=true', - if (testFile != null) '-Ptarget=${testFile.path}', - if (extraOptions != null) '-Pextra-front-end-options=$extraOptions', - if (extraOptions != null) '-Pextra-gen-snapshot-options=$extraOptions', - ], - ); - - if (exitCode != 0) { - return false; - } - return true; - } - - /// Finds and returns all integration test files for [example]. - Iterable _findIntegrationTestFiles(RepositoryPackage example) sync* { - final Directory integrationTestDir = - example.directory.childDirectory('integration_test'); - - if (!integrationTestDir.existsSync()) { - return; - } - - yield* integrationTestDir - .listSync(recursive: true) - .where((FileSystemEntity file) => - file is File && file.basename.endsWith('_test.dart')) - .cast(); - } - - /// Returns true if any of the test files in [uiTestDirectory] contain the - /// annotation that means that the test will reports the results of running - /// the Dart integration tests. - Future _testsContainDartIntegrationTestRunner( - Directory uiTestDirectory) async { - return uiTestDirectory - .list(recursive: true, followLinks: false) - .where((FileSystemEntity entity) => entity is File) - .cast() - .any((File file) { - return file.basename.endsWith('.java') && - file.readAsStringSync().contains('@RunWith(FlutterTestRunner.class)'); - }); - } -} diff --git a/script/tool/lib/src/fix_command.dart b/script/tool/lib/src/fix_command.dart deleted file mode 100644 index 2819609eabbd..000000000000 --- a/script/tool/lib/src/fix_command.dart +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2013 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. - -import 'dart:async'; - -import 'package:file/file.dart'; -import 'package:platform/platform.dart'; - -import 'common/core.dart'; -import 'common/package_looping_command.dart'; -import 'common/process_runner.dart'; -import 'common/repository_package.dart'; - -/// A command to run Dart's "fix" command on packages. -class FixCommand extends PackageLoopingCommand { - /// Creates a fix command instance. - FixCommand( - Directory packagesDir, { - ProcessRunner processRunner = const ProcessRunner(), - Platform platform = const LocalPlatform(), - }) : super(packagesDir, processRunner: processRunner, platform: platform); - - @override - final String name = 'fix'; - - @override - final String description = 'Fixes packages using dart fix.\n\n' - 'This command requires "dart" and "flutter" to be in your path, and ' - 'assumes that dependencies have already been fetched (e.g., by running ' - 'the analyze command first).'; - - @override - final bool hasLongOutput = false; - - @override - PackageLoopingType get packageLoopingType => - PackageLoopingType.includeAllSubpackages; - - @override - Future runForPackage(RepositoryPackage package) async { - final int exitCode = await processRunner.runAndStream( - 'dart', ['fix', '--apply'], - workingDir: package.directory); - if (exitCode != 0) { - printError('Unable to automatically fix package.'); - return PackageResult.fail(); - } - return PackageResult.success(); - } -} diff --git a/script/tool/lib/src/format_command.dart b/script/tool/lib/src/format_command.dart deleted file mode 100644 index e4236878658c..000000000000 --- a/script/tool/lib/src/format_command.dart +++ /dev/null @@ -1,369 +0,0 @@ -// Copyright 2013 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. - -import 'dart:convert'; -import 'dart:io' as io; - -import 'package:file/file.dart'; -import 'package:http/http.dart' as http; -import 'package:meta/meta.dart'; -import 'package:platform/platform.dart'; - -import 'common/core.dart'; -import 'common/package_command.dart'; -import 'common/process_runner.dart'; - -/// In theory this should be 8191, but in practice that was still resulting in -/// "The input line is too long" errors. This was chosen as a value that worked -/// in practice in testing with flutter/plugins, but may need to be adjusted -/// based on further experience. -@visibleForTesting -const int windowsCommandLineMax = 8000; - -/// This value is picked somewhat arbitrarily based on checking `ARG_MAX` on a -/// macOS and Linux machine. If anyone encounters a lower limit in pratice, it -/// can be lowered accordingly. -@visibleForTesting -const int nonWindowsCommandLineMax = 1000000; - -const int _exitClangFormatFailed = 3; -const int _exitFlutterFormatFailed = 4; -const int _exitJavaFormatFailed = 5; -const int _exitGitFailed = 6; -const int _exitDependencyMissing = 7; - -final Uri _googleFormatterUrl = Uri.https('github.com', - '/google/google-java-format/releases/download/google-java-format-1.3/google-java-format-1.3-all-deps.jar'); - -/// A command to format all package code. -class FormatCommand extends PackageCommand { - /// Creates an instance of the format command. - FormatCommand( - Directory packagesDir, { - ProcessRunner processRunner = const ProcessRunner(), - Platform platform = const LocalPlatform(), - }) : super(packagesDir, processRunner: processRunner, platform: platform) { - argParser.addFlag('fail-on-change', hide: true); - argParser.addOption('clang-format', - defaultsTo: 'clang-format', help: 'Path to "clang-format" executable.'); - argParser.addOption('java', - defaultsTo: 'java', help: 'Path to "java" executable.'); - } - - @override - final String name = 'format'; - - @override - final String description = - 'Formats the code of all packages (Java, Objective-C, C++, and Dart).\n\n' - 'This command requires "git", "flutter" and "clang-format" v5 to be in ' - 'your path.'; - - @override - Future run() async { - final String googleFormatterPath = await _getGoogleFormatterPath(); - - // This class is not based on PackageLoopingCommand because running the - // formatters separately for each package is an order of magnitude slower, - // due to the startup overhead of the formatters. - final Iterable files = - await _getFilteredFilePaths(getFiles(), relativeTo: packagesDir); - await _formatDart(files); - await _formatJava(files, googleFormatterPath); - await _formatCppAndObjectiveC(files); - - if (getBoolArg('fail-on-change')) { - final bool modified = await _didModifyAnything(); - if (modified) { - throw ToolExit(exitCommandFoundErrors); - } - } - } - - Future _didModifyAnything() async { - final io.ProcessResult modifiedFiles = await processRunner.run( - 'git', - ['ls-files', '--modified'], - workingDir: packagesDir, - logOnError: true, - ); - if (modifiedFiles.exitCode != 0) { - printError('Unable to determine changed files.'); - throw ToolExit(_exitGitFailed); - } - - print('\n\n'); - - final String stdout = modifiedFiles.stdout as String; - if (stdout.isEmpty) { - print('All files formatted correctly.'); - return false; - } - - print('These files are not formatted correctly (see diff below):'); - LineSplitter.split(stdout).map((String line) => ' $line').forEach(print); - - print('\nTo fix run "dart pub global activate flutter_plugin_tools && ' - 'dart pub global run flutter_plugin_tools format" or copy-paste ' - 'this command into your terminal:'); - - final io.ProcessResult diff = await processRunner.run( - 'git', - ['diff'], - workingDir: packagesDir, - logOnError: true, - ); - if (diff.exitCode != 0) { - printError('Unable to determine diff.'); - throw ToolExit(_exitGitFailed); - } - print('patch -p1 < _formatCppAndObjectiveC(Iterable files) async { - final Iterable clangFiles = _getPathsWithExtensions( - files, {'.h', '.m', '.mm', '.cc', '.cpp'}); - if (clangFiles.isNotEmpty) { - final String clangFormat = await _findValidClangFormat(); - - print('Formatting .cc, .cpp, .h, .m, and .mm files...'); - final int exitCode = await _runBatched( - clangFormat, ['-i', '--style=file'], - files: clangFiles); - if (exitCode != 0) { - printError( - 'Failed to format C, C++, and Objective-C files: exit code $exitCode.'); - throw ToolExit(_exitClangFormatFailed); - } - } - } - - Future _findValidClangFormat() async { - final String clangFormatArg = getStringArg('clang-format'); - if (await _hasDependency(clangFormatArg)) { - return clangFormatArg; - } - - // There is a known issue where "chromium/depot_tools/clang-format" - // fails with "Problem while looking for clang-format in Chromium source tree". - // Loop through all "clang-format"s in PATH until a working one is found, - // for example "/usr/local/bin/clang-format" or a "brew" installed version. - for (final String clangFormatPath in await _whichAll('clang-format')) { - if (await _hasDependency(clangFormatPath)) { - return clangFormatPath; - } - } - printError('Unable to run "clang-format". Make sure that it is in your ' - 'path, or provide a full path with --clang-format.'); - throw ToolExit(_exitDependencyMissing); - } - - Future _formatJava( - Iterable files, String googleFormatterPath) async { - final Iterable javaFiles = - _getPathsWithExtensions(files, {'.java'}); - if (javaFiles.isNotEmpty) { - final String java = getStringArg('java'); - if (!await _hasDependency(java)) { - printError( - 'Unable to run "java". Make sure that it is in your path, or ' - 'provide a full path with --java.'); - throw ToolExit(_exitDependencyMissing); - } - - print('Formatting .java files...'); - final int exitCode = await _runBatched( - java, ['-jar', googleFormatterPath, '--replace'], - files: javaFiles); - if (exitCode != 0) { - printError('Failed to format Java files: exit code $exitCode.'); - throw ToolExit(_exitJavaFormatFailed); - } - } - } - - Future _formatDart(Iterable files) async { - final Iterable dartFiles = - _getPathsWithExtensions(files, {'.dart'}); - if (dartFiles.isNotEmpty) { - print('Formatting .dart files...'); - final int exitCode = - await _runBatched('dart', ['format'], files: dartFiles); - if (exitCode != 0) { - printError('Failed to format Dart files: exit code $exitCode.'); - throw ToolExit(_exitFlutterFormatFailed); - } - } - } - - /// Given a stream of [files], returns the paths of any that are not in known - /// locations to ignore, relative to [relativeTo]. - Future> _getFilteredFilePaths( - Stream files, { - required Directory relativeTo, - }) async { - // Returns a pattern to check for [directories] as a subset of a file path. - RegExp pathFragmentForDirectories(List directories) { - String s = path.separator; - // Escape the separator for use in the regex. - if (s == r'\') { - s = r'\\'; - } - return RegExp('(?:^|$s)${path.joinAll(directories)}$s'); - } - - final String fromPath = relativeTo.path; - - // Dart files are allowed to have a pragma to disable auto-formatting. This - // was added because Hixie hurts when dealing with what dartfmt does to - // artisanally-formatted Dart, while Stuart gets really frustrated when - // dealing with PRs from newer contributors who don't know how to make Dart - // readable. After much discussion, it was decided that files in the plugins - // and packages repos that really benefit from hand-formatting (e.g. files - // with large blobs of hex literals) could be opted-out of the requirement - // that they be autoformatted, so long as the code's owner was willing to - // bear the cost of this during code reviews. - // In the event that code ownership moves to someone who does not hold the - // same views as the original owner, the pragma can be removed and the file - // auto-formatted. - const String handFormattedExtension = '.dart'; - const String handFormattedPragma = '// This file is hand-formatted.'; - - return files - .where((File file) { - // See comment above near [handFormattedPragma]. - return path.extension(file.path) != handFormattedExtension || - !file.readAsLinesSync().contains(handFormattedPragma); - }) - .map((File file) => path.relative(file.path, from: fromPath)) - .where((String path) => - // Ignore files in build/ directories (e.g., headers of frameworks) - // to avoid useless extra work in local repositories. - !path.contains( - pathFragmentForDirectories(['example', 'build'])) && - // Ignore files in Pods, which are not part of the repository. - !path.contains(pathFragmentForDirectories(['Pods'])) && - // Ignore .dart_tool/, which can have various intermediate files. - !path.contains(pathFragmentForDirectories(['.dart_tool']))) - .toList(); - } - - Iterable _getPathsWithExtensions( - Iterable files, Set extensions) { - return files.where( - (String filePath) => extensions.contains(path.extension(filePath))); - } - - Future _getGoogleFormatterPath() async { - final String javaFormatterPath = path.join( - path.dirname(path.fromUri(platform.script)), - 'google-java-format-1.3-all-deps.jar'); - final File javaFormatterFile = - packagesDir.fileSystem.file(javaFormatterPath); - - if (!javaFormatterFile.existsSync()) { - print('Downloading Google Java Format...'); - final http.Response response = await http.get(_googleFormatterUrl); - javaFormatterFile.writeAsBytesSync(response.bodyBytes); - } - - return javaFormatterPath; - } - - /// Returns true if [command] can be run successfully. - Future _hasDependency(String command) async { - // Some versions of Java accept both -version and --version, but some only - // accept -version. - final String versionFlag = command == 'java' ? '-version' : '--version'; - try { - final io.ProcessResult result = - await processRunner.run(command, [versionFlag]); - if (result.exitCode != 0) { - return false; - } - } on io.ProcessException { - // Thrown when the binary is missing entirely. - return false; - } - return true; - } - - /// Returns all instances of [command] executable found on user path. - Future> _whichAll(String command) async { - try { - final io.ProcessResult result = - await processRunner.run('which', ['-a', command]); - - if (result.exitCode != 0) { - return []; - } - - final String stdout = (result.stdout as String).trim(); - if (stdout.isEmpty) { - return []; - } - return LineSplitter.split(stdout).toList(); - } on io.ProcessException { - return []; - } - } - - /// Runs [command] on [arguments] on all of the files in [files], batched as - /// necessary to avoid OS command-line length limits. - /// - /// Returns the exit code of the first failure, which stops the run, or 0 - /// on success. - Future _runBatched( - String command, - List arguments, { - required Iterable files, - }) async { - final int commandLineMax = - platform.isWindows ? windowsCommandLineMax : nonWindowsCommandLineMax; - - // Compute the max length of the file argument portion of a batch. - // Add one to each argument's length for the space before it. - final int argumentTotalLength = - arguments.fold(0, (int sum, String arg) => sum + arg.length + 1); - final int batchMaxTotalLength = - commandLineMax - command.length - argumentTotalLength; - - // Run the command in batches. - final List> batches = - _partitionFileList(files, maxStringLength: batchMaxTotalLength); - for (final List batch in batches) { - batch.sort(); // For ease of testing. - final int exitCode = await processRunner.runAndStream( - command, [...arguments, ...batch], - workingDir: packagesDir); - if (exitCode != 0) { - return exitCode; - } - } - return 0; - } - - /// Partitions [files] into batches whose max string length as parameters to - /// a command (including the spaces between them, and between the list and - /// the command itself) is no longer than [maxStringLength]. - List> _partitionFileList(Iterable files, - {required int maxStringLength}) { - final List> batches = >[[]]; - int currentBatchTotalLength = 0; - for (final String file in files) { - final int length = file.length + 1 /* for the space */; - if (currentBatchTotalLength + length > maxStringLength) { - // Start a new batch. - batches.add([]); - currentBatchTotalLength = 0; - } - batches.last.add(file); - currentBatchTotalLength += length; - } - return batches; - } -} diff --git a/script/tool/lib/src/license_check_command.dart b/script/tool/lib/src/license_check_command.dart deleted file mode 100644 index 0517bcf43298..000000000000 --- a/script/tool/lib/src/license_check_command.dart +++ /dev/null @@ -1,308 +0,0 @@ -// Copyright 2013 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. - -import 'package:file/file.dart'; -import 'package:git/git.dart'; -import 'package:path/path.dart' as p; -import 'package:platform/platform.dart'; - -import 'common/core.dart'; -import 'common/package_command.dart'; - -const Set _codeFileExtensions = { - '.c', - '.cc', - '.cpp', - '.dart', - '.h', - '.html', - '.java', - '.kt', - '.m', - '.mm', - '.swift', - '.sh', -}; - -// Basenames without extensions of files to ignore. -const Set _ignoreBasenameList = { - 'flutter_export_environment', - 'GeneratedPluginRegistrant', - 'generated_plugin_registrant', -}; - -// File suffixes that otherwise match _codeFileExtensions to ignore. -const Set _ignoreSuffixList = { - '.g.dart', // Generated API code. - '.mocks.dart', // Generated by Mockito. -}; - -// Full basenames of files to ignore. -const Set _ignoredFullBasenameList = { - 'resource.h', // Generated by VS. -}; - -// Copyright and license regexes for third-party code. -// -// These are intentionally very simple, since there is very little third-party -// code in this repository. Complexity can be added as-needed on a case-by-case -// basis. -// -// When adding license regexes here, include the copyright info to ensure that -// any new additions are flagged for added scrutiny in review. -final List _thirdPartyLicenseBlockRegexes = [ - // Third-party code used in url_launcher_web. - RegExp( - r'^// Copyright 2017 Workiva Inc\..*' - r'^// Licensed under the Apache License, Version 2\.0', - multiLine: true, - dotAll: true, - ), - // Third-party code used in google_maps_flutter_web. - RegExp( - r'^// The MIT License [^C]+ Copyright \(c\) 2008 Krasimir Tsonev', - multiLine: true, - ), - // bsdiff in flutter/packages. - RegExp( - r'// Copyright 2003-2005 Colin Percival\. All rights reserved\.\n' - r'// Use of this source code is governed by a BSD-style license that can be\n' - r'// found in the LICENSE file\.\n', - ), -]; - -// The exact format of the BSD license that our license files should contain. -// Slight variants are not accepted because they may prevent consolidation in -// tools that assemble all licenses used in distributed applications. -// standardized. -const String _fullBsdLicenseText = ''' -Copyright 2013 The Flutter Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - * Neither the name of Google Inc. nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -'''; - -/// Validates that code files have copyright and license blocks. -class LicenseCheckCommand extends PackageCommand { - /// Creates a new license check command for [packagesDir]. - LicenseCheckCommand(Directory packagesDir, - {Platform platform = const LocalPlatform(), GitDir? gitDir}) - : super(packagesDir, platform: platform, gitDir: gitDir); - - @override - final String name = 'license-check'; - - @override - final String description = - 'Ensures that all code files have copyright/license blocks.'; - - @override - Future run() async { - // Create a set of absolute paths to submodule directories, with trailing - // separator, to do prefix matching with to test directory inclusion. - final Iterable submodulePaths = (await _getSubmoduleDirectories()) - .map( - (Directory dir) => '${dir.absolute.path}${platform.pathSeparator}'); - - final Iterable allFiles = (await _getAllFiles()).where( - (File file) => !submodulePaths.any(file.absolute.path.startsWith)); - - final Iterable codeFiles = allFiles.where((File file) => - _codeFileExtensions.contains(p.extension(file.path)) && - !_shouldIgnoreFile(file)); - final Iterable firstPartyLicenseFiles = allFiles.where((File file) => - path.basename(file.basename) == 'LICENSE' && !_isThirdParty(file)); - - final List licenseFileFailures = - await _checkLicenseFiles(firstPartyLicenseFiles); - final Map<_LicenseFailureType, List> codeFileFailures = - await _checkCodeLicenses(codeFiles); - - bool passed = true; - - print('\n=======================================\n'); - - if (licenseFileFailures.isNotEmpty) { - passed = false; - printError( - 'The following LICENSE files do not follow the expected format:'); - for (final File file in licenseFileFailures) { - printError(' ${file.path}'); - } - printError('Please ensure that they use the exact format used in this ' - 'repository".\n'); - } - - if (codeFileFailures[_LicenseFailureType.incorrectFirstParty]!.isNotEmpty) { - passed = false; - printError('The license block for these files is missing or incorrect:'); - for (final File file - in codeFileFailures[_LicenseFailureType.incorrectFirstParty]!) { - printError(' ${file.path}'); - } - printError( - 'If this third-party code, move it to a "third_party/" directory, ' - 'otherwise ensure that you are using the exact copyright and license ' - 'text used by all first-party files in this repository.\n'); - } - - if (codeFileFailures[_LicenseFailureType.unknownThirdParty]!.isNotEmpty) { - passed = false; - printError( - 'No recognized license was found for the following third-party files:'); - for (final File file - in codeFileFailures[_LicenseFailureType.unknownThirdParty]!) { - printError(' ${file.path}'); - } - print('Please check that they have a license at the top of the file. ' - 'If they do, the license check needs to be updated to recognize ' - 'the new third-party license block.\n'); - } - - if (!passed) { - throw ToolExit(1); - } - - printSuccess('All files passed validation!'); - } - - // Creates the expected copyright+license block for first-party code. - String _generateLicenseBlock( - String comment, { - String prefix = '', - String suffix = '', - }) { - return '$prefix${comment}Copyright 2013 The Flutter Authors. All rights reserved.\n' - '${comment}Use of this source code is governed by a BSD-style license that can be\n' - '${comment}found in the LICENSE file.$suffix\n'; - } - - /// Checks all license blocks for [codeFiles], returning any that fail - /// validation. - Future>> _checkCodeLicenses( - Iterable codeFiles) async { - final List incorrectFirstPartyFiles = []; - final List unrecognizedThirdPartyFiles = []; - - // Most code file types in the repository use '//' comments. - final String defaultFirstParyLicenseBlock = _generateLicenseBlock('// '); - // A few file types have a different comment structure. - final Map firstPartyLicenseBlockByExtension = - { - '.sh': _generateLicenseBlock('# '), - '.html': _generateLicenseBlock('', prefix: ''), - }; - - for (final File file in codeFiles) { - print('Checking ${file.path}'); - // On Windows, git may auto-convert line endings on checkout; this should - // still pass since they will be converted back on commit. - final String content = - (await file.readAsString()).replaceAll('\r\n', '\n'); - - final String firstParyLicense = - firstPartyLicenseBlockByExtension[p.extension(file.path)] ?? - defaultFirstParyLicenseBlock; - if (_isThirdParty(file)) { - // Third-party directories allow either known third-party licenses, our - // the first-party license, as there may be local additions. - if (!_thirdPartyLicenseBlockRegexes - .any((RegExp regex) => regex.hasMatch(content)) && - !content.contains(firstParyLicense)) { - unrecognizedThirdPartyFiles.add(file); - } - } else { - if (!content.contains(firstParyLicense)) { - incorrectFirstPartyFiles.add(file); - } - } - } - - // Sort by path for more usable output. - int pathCompare(File a, File b) => a.path.compareTo(b.path); - incorrectFirstPartyFiles.sort(pathCompare); - unrecognizedThirdPartyFiles.sort(pathCompare); - - return <_LicenseFailureType, List>{ - _LicenseFailureType.incorrectFirstParty: incorrectFirstPartyFiles, - _LicenseFailureType.unknownThirdParty: unrecognizedThirdPartyFiles, - }; - } - - /// Checks all provided LICENSE [files], returning any that fail validation. - Future> _checkLicenseFiles(Iterable files) async { - final List incorrectLicenseFiles = []; - - for (final File file in files) { - print('Checking ${file.path}'); - // On Windows, git may auto-convert line endings on checkout; this should - // still pass since they will be converted back on commit. - final String contents = file.readAsStringSync().replaceAll('\r\n', '\n'); - if (!contents.contains(_fullBsdLicenseText)) { - incorrectLicenseFiles.add(file); - } - } - - return incorrectLicenseFiles; - } - - bool _shouldIgnoreFile(File file) { - final String path = file.path; - return _ignoreBasenameList.contains(p.basenameWithoutExtension(path)) || - _ignoreSuffixList.any((String suffix) => - path.endsWith(suffix) || - _ignoredFullBasenameList.contains(p.basename(path))); - } - - bool _isThirdParty(File file) { - return path.split(file.path).contains('third_party'); - } - - Future> _getAllFiles() => packagesDir.parent - .list(recursive: true, followLinks: false) - .where((FileSystemEntity entity) => entity is File) - .map((FileSystemEntity file) => file as File) - .toList(); - - // Returns the directories containing mapped submodules, if any. - Future> _getSubmoduleDirectories() async { - final List submodulePaths = []; - final Directory repoRoot = - packagesDir.fileSystem.directory((await gitDir).path); - final File submoduleSpec = repoRoot.childFile('.gitmodules'); - if (submoduleSpec.existsSync()) { - final RegExp pathLine = RegExp(r'path\s*=\s*(.*)'); - for (final String line in submoduleSpec.readAsLinesSync()) { - final RegExpMatch? match = pathLine.firstMatch(line); - if (match != null) { - submodulePaths.add(repoRoot.childDirectory(match.group(1)!.trim())); - } - } - } - return submodulePaths; - } -} - -enum _LicenseFailureType { incorrectFirstParty, unknownThirdParty } diff --git a/script/tool/lib/src/lint_android_command.dart b/script/tool/lib/src/lint_android_command.dart deleted file mode 100644 index eb78ce891685..000000000000 --- a/script/tool/lib/src/lint_android_command.dart +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright 2013 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. - -import 'package:file/file.dart'; -import 'package:platform/platform.dart'; - -import 'common/core.dart'; -import 'common/gradle.dart'; -import 'common/package_looping_command.dart'; -import 'common/plugin_utils.dart'; -import 'common/process_runner.dart'; -import 'common/repository_package.dart'; - -/// Run 'gradlew lint'. -/// -/// See https://developer.android.com/studio/write/lint. -class LintAndroidCommand extends PackageLoopingCommand { - /// Creates an instance of the linter command. - LintAndroidCommand( - Directory packagesDir, { - ProcessRunner processRunner = const ProcessRunner(), - Platform platform = const LocalPlatform(), - }) : super(packagesDir, processRunner: processRunner, platform: platform); - - @override - final String name = 'lint-android'; - - @override - final String description = 'Runs "gradlew lint" on Android plugins.\n\n' - 'Requires the examples to have been build at least once before running.'; - - @override - Future runForPackage(RepositoryPackage package) async { - if (!pluginSupportsPlatform(platformAndroid, package, - requiredMode: PlatformSupport.inline)) { - return PackageResult.skip( - 'Plugin does not have an Android implementation.'); - } - - bool failed = false; - for (final RepositoryPackage example in package.getExamples()) { - final GradleProject project = GradleProject(example, - processRunner: processRunner, platform: platform); - - if (!project.isConfigured()) { - return PackageResult.fail(['Build examples before linting']); - } - - final String packageName = package.directory.basename; - - // Only lint one build mode to avoid extra work. - // Only lint the plugin project itself, to avoid failing due to errors in - // dependencies. - // - // TODO(stuartmorgan): Consider adding an XML parser to read and summarize - // all results. Currently, only the first three errors will be shown - // inline, and the rest have to be checked via the CI-uploaded artifact. - final int exitCode = await project.runCommand('$packageName:lintDebug'); - if (exitCode != 0) { - failed = true; - } - } - - return failed ? PackageResult.fail() : PackageResult.success(); - } -} diff --git a/script/tool/lib/src/list_command.dart b/script/tool/lib/src/list_command.dart deleted file mode 100644 index b47657e47eff..000000000000 --- a/script/tool/lib/src/list_command.dart +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright 2013 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. - -import 'package:file/file.dart'; -import 'package:platform/platform.dart'; - -import 'common/package_command.dart'; -import 'common/repository_package.dart'; - -/// A command to list different types of repository content. -class ListCommand extends PackageCommand { - /// Creates an instance of the list command, whose behavior depends on the - /// 'type' argument it provides. - ListCommand( - Directory packagesDir, { - Platform platform = const LocalPlatform(), - }) : super(packagesDir, platform: platform) { - argParser.addOption( - _type, - defaultsTo: _package, - allowed: [_package, _example, _allPackage, _file], - help: 'What type of file system content to list.', - ); - } - - static const String _type = 'type'; - static const String _allPackage = 'package-or-subpackage'; - static const String _example = 'example'; - static const String _package = 'package'; - static const String _file = 'file'; - - @override - final String name = 'list'; - - @override - final String description = 'Lists packages or files'; - - @override - Future run() async { - switch (getStringArg(_type)) { - case _package: - await for (final PackageEnumerationEntry entry in getTargetPackages()) { - print(entry.package.path); - } - break; - case _example: - final Stream examples = getTargetPackages() - .expand( - (PackageEnumerationEntry entry) => entry.package.getExamples()); - await for (final RepositoryPackage package in examples) { - print(package.path); - } - break; - case _allPackage: - await for (final PackageEnumerationEntry entry - in getTargetPackagesAndSubpackages()) { - print(entry.package.path); - } - break; - case _file: - await for (final File file in getFiles()) { - print(file.path); - } - break; - } - } -} diff --git a/script/tool/lib/src/main.dart b/script/tool/lib/src/main.dart index 0083e0cbb8ee..6b421ebaebc0 100644 --- a/script/tool/lib/src/main.dart +++ b/script/tool/lib/src/main.dart @@ -9,34 +9,19 @@ import 'package:file/file.dart'; import 'package:file/local.dart'; import 'analyze_command.dart'; -import 'build_examples_command.dart'; import 'common/core.dart'; -import 'create_all_packages_app_command.dart'; -import 'custom_test_command.dart'; -import 'dependabot_check_command.dart'; -import 'drive_examples_command.dart'; -import 'federation_safety_check_command.dart'; -import 'firebase_test_lab_command.dart'; -import 'fix_command.dart'; -import 'format_command.dart'; -import 'license_check_command.dart'; -import 'lint_android_command.dart'; -import 'list_command.dart'; -import 'make_deps_path_based_command.dart'; -import 'native_test_command.dart'; -import 'podspec_check_command.dart'; -import 'publish_check_command.dart'; -import 'publish_command.dart'; -import 'pubspec_check_command.dart'; -import 'readme_check_command.dart'; -import 'remove_dev_dependencies.dart'; -import 'test_command.dart'; -import 'update_excerpts_command.dart'; -import 'update_release_info_command.dart'; -import 'version_check_command.dart'; -import 'xcode_analyze_command.dart'; void main(List args) { + print(''' +*** WARNING *** +This copy of the tooling is now only here as a shim for scripts in other +repositories that have not yet been updated, and can only run 'analyze'. For +full tooling in this repository, see the updated instructions: +https://github.com/flutter/packages/blob/main/script/tool/README.md +to switch to running the published version. + +'''); + const FileSystem fileSystem = LocalFileSystem(); Directory packagesDir = @@ -54,32 +39,7 @@ void main(List args) { final CommandRunner commandRunner = CommandRunner( 'dart pub global run flutter_plugin_tools', 'Productivity utils for hosting multiple plugins within one repository.') - ..addCommand(AnalyzeCommand(packagesDir)) - ..addCommand(BuildExamplesCommand(packagesDir)) - ..addCommand(CreateAllPackagesAppCommand(packagesDir)) - ..addCommand(CustomTestCommand(packagesDir)) - ..addCommand(DependabotCheckCommand(packagesDir)) - ..addCommand(DriveExamplesCommand(packagesDir)) - ..addCommand(FederationSafetyCheckCommand(packagesDir)) - ..addCommand(FirebaseTestLabCommand(packagesDir)) - ..addCommand(FixCommand(packagesDir)) - ..addCommand(FormatCommand(packagesDir)) - ..addCommand(LicenseCheckCommand(packagesDir)) - ..addCommand(LintAndroidCommand(packagesDir)) - ..addCommand(PodspecCheckCommand(packagesDir)) - ..addCommand(ListCommand(packagesDir)) - ..addCommand(NativeTestCommand(packagesDir)) - ..addCommand(MakeDepsPathBasedCommand(packagesDir)) - ..addCommand(PublishCheckCommand(packagesDir)) - ..addCommand(PublishCommand(packagesDir)) - ..addCommand(PubspecCheckCommand(packagesDir)) - ..addCommand(ReadmeCheckCommand(packagesDir)) - ..addCommand(RemoveDevDependenciesCommand(packagesDir)) - ..addCommand(TestCommand(packagesDir)) - ..addCommand(UpdateExcerptsCommand(packagesDir)) - ..addCommand(UpdateReleaseInfoCommand(packagesDir)) - ..addCommand(VersionCheckCommand(packagesDir)) - ..addCommand(XcodeAnalyzeCommand(packagesDir)); + ..addCommand(AnalyzeCommand(packagesDir)); commandRunner.run(args).catchError((Object e) { final ToolExit toolExit = e as ToolExit; diff --git a/script/tool/lib/src/make_deps_path_based_command.dart b/script/tool/lib/src/make_deps_path_based_command.dart deleted file mode 100644 index 10abcd44ae6e..000000000000 --- a/script/tool/lib/src/make_deps_path_based_command.dart +++ /dev/null @@ -1,283 +0,0 @@ -// Copyright 2013 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. - -import 'package:file/file.dart'; -import 'package:git/git.dart'; -import 'package:path/path.dart' as p; -import 'package:pub_semver/pub_semver.dart'; - -import 'common/core.dart'; -import 'common/git_version_finder.dart'; -import 'common/package_command.dart'; -import 'common/repository_package.dart'; - -const int _exitPackageNotFound = 3; -const int _exitCannotUpdatePubspec = 4; - -enum _RewriteOutcome { changed, noChangesNeeded, alreadyChanged } - -/// Converts all dependencies on target packages to path-based dependencies. -/// -/// This is to allow for pre-publish testing of changes that could affect other -/// packages in the repository. For instance, this allows for catching cases -/// where a non-breaking change to a platform interface package of a federated -/// plugin would cause post-publish analyzer failures in another package of that -/// plugin. -class MakeDepsPathBasedCommand extends PackageCommand { - /// Creates an instance of the command to convert selected dependencies to - /// path-based. - MakeDepsPathBasedCommand( - Directory packagesDir, { - GitDir? gitDir, - }) : super(packagesDir, gitDir: gitDir) { - argParser.addMultiOption(_targetDependenciesArg, - help: - 'The names of the packages to convert to path-based dependencies.\n' - 'Ignored if --$_targetDependenciesWithNonBreakingUpdatesArg is ' - 'passed.', - valueHelp: 'some_package'); - argParser.addFlag( - _targetDependenciesWithNonBreakingUpdatesArg, - help: 'Causes all packages that have non-breaking version changes ' - 'when compared against the git base to be treated as target ' - 'packages.', - ); - } - - static const String _targetDependenciesArg = 'target-dependencies'; - static const String _targetDependenciesWithNonBreakingUpdatesArg = - 'target-dependencies-with-non-breaking-updates'; - - // The comment to add to temporary dependency overrides. - static const String _dependencyOverrideWarningComment = - '# FOR TESTING ONLY. DO NOT MERGE.'; - - @override - final String name = 'make-deps-path-based'; - - @override - final String description = - 'Converts package dependencies to path-based references.'; - - @override - Future run() async { - final Set targetDependencies = - getBoolArg(_targetDependenciesWithNonBreakingUpdatesArg) - ? await _getNonBreakingUpdatePackages() - : getStringListArg(_targetDependenciesArg).toSet(); - - if (targetDependencies.isEmpty) { - print('No target dependencies; nothing to do.'); - return; - } - print('Rewriting references to: ${targetDependencies.join(', ')}...'); - - final Map localDependencyPackages = - _findLocalPackages(targetDependencies); - - final String repoRootPath = (await gitDir).path; - for (final File pubspec in await _getAllPubspecs()) { - final String displayPath = p.posix.joinAll( - path.split(path.relative(pubspec.absolute.path, from: repoRootPath))); - final _RewriteOutcome outcome = await _addDependencyOverridesIfNecessary( - pubspec, localDependencyPackages); - switch (outcome) { - case _RewriteOutcome.changed: - print(' Modified $displayPath'); - break; - case _RewriteOutcome.alreadyChanged: - print(' Skipped $displayPath - Already rewritten'); - break; - case _RewriteOutcome.noChangesNeeded: - break; - } - } - } - - Map _findLocalPackages(Set packageNames) { - final Map targets = - {}; - for (final String packageName in packageNames) { - final Directory topLevelCandidate = - packagesDir.childDirectory(packageName); - // If packages// exists, then either that directory is the - // package, or packages/// exists and is the - // package (in the case of a federated plugin). - if (topLevelCandidate.existsSync()) { - final Directory appFacingCandidate = - topLevelCandidate.childDirectory(packageName); - targets[packageName] = RepositoryPackage(appFacingCandidate.existsSync() - ? appFacingCandidate - : topLevelCandidate); - continue; - } - // If there is no packages/ directory, then either the - // packages doesn't exist, or it is a sub-package of a federated plugin. - // If it's the latter, it will be a directory whose name is a prefix. - for (final FileSystemEntity entity in packagesDir.listSync()) { - if (entity is Directory && packageName.startsWith(entity.basename)) { - final Directory subPackageCandidate = - entity.childDirectory(packageName); - if (subPackageCandidate.existsSync()) { - targets[packageName] = RepositoryPackage(subPackageCandidate); - break; - } - } - } - - if (!targets.containsKey(packageName)) { - printError('Unable to find package "$packageName"'); - throw ToolExit(_exitPackageNotFound); - } - } - return targets; - } - - /// If [pubspecFile] has any dependencies on packages in [localDependencies], - /// adds dependency_overrides entries to redirect them to the local version - /// using path-based dependencies. - Future<_RewriteOutcome> _addDependencyOverridesIfNecessary(File pubspecFile, - Map localDependencies) async { - final String pubspecContents = pubspecFile.readAsStringSync(); - final Pubspec pubspec = Pubspec.parse(pubspecContents); - // Fail if there are any dependency overrides already, other than ones - // created by this script. If support for that is needed at some point, it - // can be added, but currently it's not and relying on that makes the logic - // here much simpler. - if (pubspec.dependencyOverrides.isNotEmpty) { - if (pubspecContents.contains(_dependencyOverrideWarningComment)) { - return _RewriteOutcome.alreadyChanged; - } - printError( - 'Packages with dependency overrides are not currently supported.'); - throw ToolExit(_exitCannotUpdatePubspec); - } - - final Iterable combinedDependencies = [ - ...pubspec.dependencies.keys, - ...pubspec.devDependencies.keys, - ]; - final List packagesToOverride = combinedDependencies - .where( - (String packageName) => localDependencies.containsKey(packageName)) - .toList(); - // Sort the combined list to avoid sort_pub_dependencies lint violations. - packagesToOverride.sort(); - if (packagesToOverride.isNotEmpty) { - final String commonBasePath = packagesDir.path; - // Find the relative path to the common base. - final int packageDepth = path - .split(path.relative(pubspecFile.parent.absolute.path, - from: commonBasePath)) - .length; - final List relativeBasePathComponents = - List.filled(packageDepth, '..'); - // This is done via strings rather than by manipulating the Pubspec and - // then re-serialiazing so that it's a localized change, rather than - // rewriting the whole file (e.g., destroying comments), which could be - // more disruptive for local use. - String newPubspecContents = ''' -$pubspecContents - -$_dependencyOverrideWarningComment -dependency_overrides: -'''; - for (final String packageName in packagesToOverride) { - // Find the relative path from the common base to the local package. - final List repoRelativePathComponents = path.split( - path.relative(localDependencies[packageName]!.path, - from: commonBasePath)); - newPubspecContents += ''' - $packageName: - path: ${p.posix.joinAll([ - ...relativeBasePathComponents, - ...repoRelativePathComponents, - ])} -'''; - } - pubspecFile.writeAsStringSync(newPubspecContents); - return _RewriteOutcome.changed; - } - return _RewriteOutcome.noChangesNeeded; - } - - /// Returns all pubspecs anywhere under the packages directory. - Future> _getAllPubspecs() => packagesDir.parent - .list(recursive: true, followLinks: false) - .where((FileSystemEntity entity) => - entity is File && p.basename(entity.path) == 'pubspec.yaml') - .map((FileSystemEntity file) => file as File) - .toList(); - - /// Returns all packages that have non-breaking published changes (i.e., a - /// minor or bugfix version change) relative to the git comparison base. - /// - /// Prints status information about what was checked for ease of auditing logs - /// in CI. - Future> _getNonBreakingUpdatePackages() async { - final GitVersionFinder gitVersionFinder = await retrieveVersionFinder(); - final String baseSha = await gitVersionFinder.getBaseSha(); - print('Finding changed packages relative to "$baseSha"...'); - - final Set changedPackages = {}; - for (final String changedPath in await gitVersionFinder.getChangedFiles()) { - // Git output always uses Posix paths. - final List allComponents = p.posix.split(changedPath); - // Only pubspec changes are potential publishing events. - if (allComponents.last != 'pubspec.yaml' || - allComponents.contains('example')) { - continue; - } - if (!allComponents.contains(packagesDir.basename)) { - print(' Skipping $changedPath; not in packages directory.'); - continue; - } - final RepositoryPackage package = - RepositoryPackage(packagesDir.fileSystem.file(changedPath).parent); - // Ignored deleted packages, as they won't be published. - if (!package.pubspecFile.existsSync()) { - final String directoryName = p.posix.joinAll(path.split(path.relative( - package.directory.absolute.path, - from: packagesDir.path))); - print(' Skipping $directoryName; deleted.'); - continue; - } - final String packageName = package.parsePubspec().name; - if (!await _hasNonBreakingVersionChange(package)) { - // Log packages that had pubspec changes but weren't included for ease - // of auditing CI. - print(' Skipping $packageName; no non-breaking version change.'); - continue; - } - changedPackages.add(packageName); - } - return changedPackages; - } - - Future _hasNonBreakingVersionChange(RepositoryPackage package) async { - final Pubspec pubspec = package.parsePubspec(); - if (pubspec.publishTo == 'none') { - return false; - } - - final String pubspecGitPath = p.posix.joinAll(path.split(path.relative( - package.pubspecFile.absolute.path, - from: (await gitDir).path))); - final GitVersionFinder gitVersionFinder = await retrieveVersionFinder(); - final Version? previousVersion = - await gitVersionFinder.getPackageVersion(pubspecGitPath); - if (previousVersion == null) { - // The plugin is new, so nothing can be depending on it yet. - return false; - } - final Version newVersion = pubspec.version!; - if ((newVersion.major > 0 && newVersion.major != previousVersion.major) || - (newVersion.major == 0 && newVersion.minor != previousVersion.minor)) { - // Breaking changes aren't targetted since they won't be picked up - // automatically. - return false; - } - return newVersion != previousVersion; - } -} diff --git a/script/tool/lib/src/native_test_command.dart b/script/tool/lib/src/native_test_command.dart deleted file mode 100644 index af5f4df98e86..000000000000 --- a/script/tool/lib/src/native_test_command.dart +++ /dev/null @@ -1,624 +0,0 @@ -// Copyright 2013 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. - -import 'package:file/file.dart'; -import 'package:platform/platform.dart'; - -import 'common/cmake.dart'; -import 'common/core.dart'; -import 'common/gradle.dart'; -import 'common/package_looping_command.dart'; -import 'common/plugin_utils.dart'; -import 'common/process_runner.dart'; -import 'common/repository_package.dart'; -import 'common/xcode.dart'; - -const String _unitTestFlag = 'unit'; -const String _integrationTestFlag = 'integration'; - -const String _iOSDestinationFlag = 'ios-destination'; - -const int _exitNoIOSSimulators = 3; - -/// The command to run native tests for plugins: -/// - iOS and macOS: XCTests (XCUnitTest and XCUITest) -/// - Android: JUnit tests -/// - Windows and Linux: GoogleTest tests -class NativeTestCommand extends PackageLoopingCommand { - /// Creates an instance of the test command. - NativeTestCommand( - Directory packagesDir, { - ProcessRunner processRunner = const ProcessRunner(), - Platform platform = const LocalPlatform(), - }) : _xcode = Xcode(processRunner: processRunner, log: true), - super(packagesDir, processRunner: processRunner, platform: platform) { - argParser.addOption( - _iOSDestinationFlag, - help: 'Specify the destination when running iOS tests.\n' - 'This is passed to the `-destination` argument in the xcodebuild command.\n' - 'See https://developer.apple.com/library/archive/technotes/tn2339/_index.html#//apple_ref/doc/uid/DTS40014588-CH1-UNIT ' - 'for details on how to specify the destination.', - ); - argParser.addFlag(platformAndroid, help: 'Runs Android tests'); - argParser.addFlag(platformIOS, help: 'Runs iOS tests'); - argParser.addFlag(platformLinux, help: 'Runs Linux tests'); - argParser.addFlag(platformMacOS, help: 'Runs macOS tests'); - argParser.addFlag(platformWindows, help: 'Runs Windows tests'); - - // By default, both unit tests and integration tests are run, but provide - // flags to disable one or the other. - argParser.addFlag(_unitTestFlag, - help: 'Runs native unit tests', defaultsTo: true); - argParser.addFlag(_integrationTestFlag, - help: 'Runs native integration (UI) tests', defaultsTo: true); - } - - // The device destination flags for iOS tests. - List _iOSDestinationFlags = []; - - final Xcode _xcode; - - @override - final String name = 'native-test'; - - @override - final String description = ''' -Runs native unit tests and native integration tests. - -Currently supported platforms: -- Android -- iOS: requires 'xcrun' to be in your path. -- Linux (unit tests only) -- macOS: requires 'xcrun' to be in your path. -- Windows (unit tests only) - -The example app(s) must be built for all targeted platforms before running -this command. -'''; - - Map _platforms = {}; - - List _requestedPlatforms = []; - - @override - Future initializeRun() async { - _platforms = { - platformAndroid: _PlatformDetails('Android', _testAndroid), - platformIOS: _PlatformDetails('iOS', _testIOS), - platformLinux: _PlatformDetails('Linux', _testLinux), - platformMacOS: _PlatformDetails('macOS', _testMacOS), - platformWindows: _PlatformDetails('Windows', _testWindows), - }; - _requestedPlatforms = _platforms.keys - .where((String platform) => getBoolArg(platform)) - .toList(); - _requestedPlatforms.sort(); - - if (_requestedPlatforms.isEmpty) { - printError('At least one platform flag must be provided.'); - throw ToolExit(exitInvalidArguments); - } - - if (!(getBoolArg(_unitTestFlag) || getBoolArg(_integrationTestFlag))) { - printError('At least one test type must be enabled.'); - throw ToolExit(exitInvalidArguments); - } - - if (getBoolArg(platformWindows) && getBoolArg(_integrationTestFlag)) { - logWarning('This command currently only supports unit tests for Windows. ' - 'See https://github.com/flutter/flutter/issues/70233.'); - } - - if (getBoolArg(platformLinux) && getBoolArg(_integrationTestFlag)) { - logWarning('This command currently only supports unit tests for Linux. ' - 'See https://github.com/flutter/flutter/issues/70235.'); - } - - // iOS-specific run-level state. - if (_requestedPlatforms.contains('ios')) { - String destination = getStringArg(_iOSDestinationFlag); - if (destination.isEmpty) { - final String? simulatorId = - await _xcode.findBestAvailableIphoneSimulator(); - if (simulatorId == null) { - printError('Cannot find any available iOS simulators.'); - throw ToolExit(_exitNoIOSSimulators); - } - destination = 'id=$simulatorId'; - } - _iOSDestinationFlags = [ - '-destination', - destination, - ]; - } - } - - @override - Future runForPackage(RepositoryPackage package) async { - final List testPlatforms = []; - for (final String platform in _requestedPlatforms) { - if (!pluginSupportsPlatform(platform, package, - requiredMode: PlatformSupport.inline)) { - print('No implementation for ${_platforms[platform]!.label}.'); - continue; - } - if (!pluginHasNativeCodeForPlatform(platform, package)) { - print('No native code for ${_platforms[platform]!.label}.'); - continue; - } - testPlatforms.add(platform); - } - - if (testPlatforms.isEmpty) { - return PackageResult.skip('Nothing to test for target platform(s).'); - } - - final _TestMode mode = _TestMode( - unit: getBoolArg(_unitTestFlag), - integration: getBoolArg(_integrationTestFlag), - ); - - bool ranTests = false; - bool failed = false; - final List failureMessages = []; - for (final String platform in testPlatforms) { - final _PlatformDetails platformInfo = _platforms[platform]!; - print('Running tests for ${platformInfo.label}...'); - print('----------------------------------------'); - final _PlatformResult result = - await platformInfo.testFunction(package, mode); - ranTests |= result.state != RunState.skipped; - if (result.state == RunState.failed) { - failed = true; - - final String? error = result.error; - // Only provide the failing platforms in the failure details if testing - // multiple platforms, otherwise it's just noise. - if (_requestedPlatforms.length > 1) { - failureMessages.add(error != null - ? '${platformInfo.label}: $error' - : platformInfo.label); - } else if (error != null) { - // If there's only one platform, only provide error details in the - // summary if the platform returned a message. - failureMessages.add(error); - } - } - } - - if (!ranTests) { - return PackageResult.skip('No tests found.'); - } - return failed - ? PackageResult.fail(failureMessages) - : PackageResult.success(); - } - - Future<_PlatformResult> _testAndroid( - RepositoryPackage plugin, _TestMode mode) async { - bool exampleHasUnitTests(RepositoryPackage example) { - return example - .platformDirectory(FlutterPlatform.android) - .childDirectory('app') - .childDirectory('src') - .childDirectory('test') - .existsSync() || - plugin - .platformDirectory(FlutterPlatform.android) - .childDirectory('src') - .childDirectory('test') - .existsSync(); - } - - bool exampleHasNativeIntegrationTests(RepositoryPackage example) { - final Directory integrationTestDirectory = example - .platformDirectory(FlutterPlatform.android) - .childDirectory('app') - .childDirectory('src') - .childDirectory('androidTest'); - // There are two types of integration tests that can be in the androidTest - // directory: - // - FlutterTestRunner.class tests, which bridge to Dart integration tests - // - Purely native tests - // Only the latter is supported by this command; the former will hang if - // run here because they will wait for a Dart call that will never come. - // - // This repository uses a convention of putting the former in a - // *ActivityTest.java file, so ignore that file when checking for tests. - // Also ignore DartIntegrationTest.java, which defines the annotation used - // below for filtering the former out when running tests. - // - // If those are the only files, then there are no tests to run here. - return integrationTestDirectory.existsSync() && - integrationTestDirectory - .listSync(recursive: true) - .whereType() - .any((File file) { - final String basename = file.basename; - return !basename.endsWith('ActivityTest.java') && - basename != 'DartIntegrationTest.java'; - }); - } - - final Iterable examples = plugin.getExamples(); - - bool ranUnitTests = false; - bool ranAnyTests = false; - bool failed = false; - bool hasMissingBuild = false; - for (final RepositoryPackage example in examples) { - final bool hasUnitTests = exampleHasUnitTests(example); - final bool hasIntegrationTests = - exampleHasNativeIntegrationTests(example); - - if (mode.unit && !hasUnitTests) { - _printNoExampleTestsMessage(example, 'Android unit'); - } - if (mode.integration && !hasIntegrationTests) { - _printNoExampleTestsMessage(example, 'Android integration'); - } - - final bool runUnitTests = mode.unit && hasUnitTests; - final bool runIntegrationTests = mode.integration && hasIntegrationTests; - if (!runUnitTests && !runIntegrationTests) { - continue; - } - - final String exampleName = example.displayName; - _printRunningExampleTestsMessage(example, 'Android'); - - final GradleProject project = GradleProject( - example, - processRunner: processRunner, - platform: platform, - ); - if (!project.isConfigured()) { - printError('ERROR: Run "flutter build apk" on $exampleName, or run ' - 'this tool\'s "build-examples --apk" command, ' - 'before executing tests.'); - failed = true; - hasMissingBuild = true; - continue; - } - - if (runUnitTests) { - print('Running unit tests...'); - final int exitCode = await project.runCommand('testDebugUnitTest'); - if (exitCode != 0) { - printError('$exampleName unit tests failed.'); - failed = true; - } - ranUnitTests = true; - ranAnyTests = true; - } - - if (runIntegrationTests) { - // FlutterTestRunner-based tests will hang forever if run in a normal - // app build, since they wait for a Dart call from integration_test that - // will never come. Those tests have an extra annotation to allow - // filtering them out. - const String filter = - 'notAnnotation=io.flutter.plugins.DartIntegrationTest'; - - print('Running integration tests...'); - final int exitCode = await project.runCommand( - 'app:connectedAndroidTest', - arguments: [ - '-Pandroid.testInstrumentationRunnerArguments.$filter', - ], - ); - if (exitCode != 0) { - printError('$exampleName integration tests failed.'); - failed = true; - } - ranAnyTests = true; - } - } - - if (failed) { - return _PlatformResult(RunState.failed, - error: hasMissingBuild - ? 'Examples must be built before testing.' - : null); - } - if (!mode.integrationOnly && !ranUnitTests) { - printError('No unit tests ran. Plugins are required to have unit tests.'); - return _PlatformResult(RunState.failed, - error: 'No unit tests ran (use --exclude if this is intentional).'); - } - if (!ranAnyTests) { - return _PlatformResult(RunState.skipped); - } - return _PlatformResult(RunState.succeeded); - } - - Future<_PlatformResult> _testIOS(RepositoryPackage plugin, _TestMode mode) { - return _runXcodeTests(plugin, 'iOS', mode, - extraFlags: _iOSDestinationFlags); - } - - Future<_PlatformResult> _testMacOS(RepositoryPackage plugin, _TestMode mode) { - return _runXcodeTests(plugin, 'macOS', mode); - } - - /// Runs all applicable tests for [plugin], printing status and returning - /// the test result. - /// - /// The tests targets must be added to the Xcode project of the example app, - /// usually at "example/{ios,macos}/Runner.xcworkspace". - Future<_PlatformResult> _runXcodeTests( - RepositoryPackage plugin, - String platform, - _TestMode mode, { - List extraFlags = const [], - }) async { - String? testTarget; - const String unitTestTarget = 'RunnerTests'; - if (mode.unitOnly) { - testTarget = unitTestTarget; - } else if (mode.integrationOnly) { - testTarget = 'RunnerUITests'; - } - - bool ranUnitTests = false; - // Assume skipped until at least one test has run. - RunState overallResult = RunState.skipped; - for (final RepositoryPackage example in plugin.getExamples()) { - final String exampleName = example.displayName; - - // If running a specific target, check that. Otherwise, check if there - // are unit tests, since having no unit tests for a plugin is fatal - // (by repo policy) even if there are integration tests. - bool exampleHasUnitTests = false; - final String? targetToCheck = - testTarget ?? (mode.unit ? unitTestTarget : null); - final Directory xcodeProject = example.directory - .childDirectory(platform.toLowerCase()) - .childDirectory('Runner.xcodeproj'); - if (targetToCheck != null) { - final bool? hasTarget = - await _xcode.projectHasTarget(xcodeProject, targetToCheck); - if (hasTarget == null) { - printError('Unable to check targets for $exampleName.'); - overallResult = RunState.failed; - continue; - } else if (!hasTarget) { - print('No "$targetToCheck" target in $exampleName; skipping.'); - continue; - } else if (targetToCheck == unitTestTarget) { - exampleHasUnitTests = true; - } - } - - _printRunningExampleTestsMessage(example, platform); - final int exitCode = await _xcode.runXcodeBuild( - example.directory, - actions: ['test'], - workspace: '${platform.toLowerCase()}/Runner.xcworkspace', - scheme: 'Runner', - configuration: 'Debug', - extraFlags: [ - if (testTarget != null) '-only-testing:$testTarget', - ...extraFlags, - 'GCC_TREAT_WARNINGS_AS_ERRORS=YES', - ], - ); - - // The exit code from 'xcodebuild test' when there are no tests. - const int xcodebuildNoTestExitCode = 66; - switch (exitCode) { - case xcodebuildNoTestExitCode: - _printNoExampleTestsMessage(example, platform); - break; - case 0: - printSuccess('Successfully ran $platform xctest for $exampleName'); - // If this is the first test, assume success until something fails. - if (overallResult == RunState.skipped) { - overallResult = RunState.succeeded; - } - if (exampleHasUnitTests) { - ranUnitTests = true; - } - break; - default: - // Any failure means a failure overall. - overallResult = RunState.failed; - // If unit tests ran, note that even if they failed. - if (exampleHasUnitTests) { - ranUnitTests = true; - } - break; - } - } - - if (!mode.integrationOnly && !ranUnitTests) { - printError('No unit tests ran. Plugins are required to have unit tests.'); - // Only return a specific summary error message about the missing unit - // tests if there weren't also failures, to avoid having a misleadingly - // specific message. - if (overallResult != RunState.failed) { - return _PlatformResult(RunState.failed, - error: 'No unit tests ran (use --exclude if this is intentional).'); - } - } - - return _PlatformResult(overallResult); - } - - Future<_PlatformResult> _testWindows( - RepositoryPackage plugin, _TestMode mode) async { - if (mode.integrationOnly) { - return _PlatformResult(RunState.skipped); - } - - bool isTestBinary(File file) { - return file.basename.endsWith('_test.exe') || - file.basename.endsWith('_tests.exe'); - } - - return _runGoogleTestTests(plugin, 'Windows', 'Debug', - isTestBinary: isTestBinary); - } - - Future<_PlatformResult> _testLinux( - RepositoryPackage plugin, _TestMode mode) async { - if (mode.integrationOnly) { - return _PlatformResult(RunState.skipped); - } - - bool isTestBinary(File file) { - return file.basename.endsWith('_test') || - file.basename.endsWith('_tests'); - } - - // Since Linux uses a single-config generator, building-examples only - // generates the build files for release, so the tests have to be run in - // release mode as well. - // - // TODO(stuartmorgan): Consider adding a command to `flutter` that would - // generate build files without doing a build, and using that instead of - // relying on running build-examples. See - // https://github.com/flutter/flutter/issues/93407. - return _runGoogleTestTests(plugin, 'Linux', 'Release', - isTestBinary: isTestBinary); - } - - /// Finds every file in the [buildDirectoryName] subdirectory of [plugin]'s - /// build directory for which [isTestBinary] is true, and runs all of them, - /// returning the overall result. - /// - /// The binaries are assumed to be Google Test test binaries, thus returning - /// zero for success and non-zero for failure. - Future<_PlatformResult> _runGoogleTestTests( - RepositoryPackage plugin, - String platformName, - String buildMode, { - required bool Function(File) isTestBinary, - }) async { - final List testBinaries = []; - bool hasMissingBuild = false; - bool buildFailed = false; - for (final RepositoryPackage example in plugin.getExamples()) { - final CMakeProject project = CMakeProject(example.directory, - buildMode: buildMode, - processRunner: processRunner, - platform: platform); - if (!project.isConfigured()) { - printError('ERROR: Run "flutter build" on ${example.displayName}, ' - 'or run this tool\'s "build-examples" command, for the target ' - 'platform before executing tests.'); - hasMissingBuild = true; - continue; - } - - // By repository convention, example projects create an aggregate target - // called 'unit_tests' that builds all unit tests (usually just an alias - // for a specific test target). - final int exitCode = await project.runBuild('unit_tests'); - if (exitCode != 0) { - printError('${example.displayName} unit tests failed to build.'); - buildFailed = true; - } - - testBinaries.addAll(project.buildDirectory - .listSync(recursive: true) - .whereType() - .where(isTestBinary) - .where((File file) { - // Only run the `buildMode` build of the unit tests, to avoid running - // the same tests multiple times. - final List components = path.split(file.path); - return components.contains(buildMode) || - components.contains(buildMode.toLowerCase()); - })); - } - - if (hasMissingBuild) { - return _PlatformResult(RunState.failed, - error: 'Examples must be built before testing.'); - } - - if (buildFailed) { - return _PlatformResult(RunState.failed, - error: 'Failed to build $platformName unit tests.'); - } - - if (testBinaries.isEmpty) { - final String binaryExtension = platform.isWindows ? '.exe' : ''; - printError( - 'No test binaries found. At least one *_test(s)$binaryExtension ' - 'binary should be built by the example(s)'); - return _PlatformResult(RunState.failed, - error: 'No $platformName unit tests found'); - } - - bool passing = true; - for (final File test in testBinaries) { - print('Running ${test.basename}...'); - final int exitCode = - await processRunner.runAndStream(test.path, []); - passing &= exitCode == 0; - } - return _PlatformResult(passing ? RunState.succeeded : RunState.failed); - } - - /// Prints a standard format message indicating that [platform] tests for - /// [plugin]'s [example] are about to be run. - void _printRunningExampleTestsMessage( - RepositoryPackage example, String platform) { - print('Running $platform tests for ${example.displayName}...'); - } - - /// Prints a standard format message indicating that no tests were found for - /// [plugin]'s [example] for [platform]. - void _printNoExampleTestsMessage(RepositoryPackage example, String platform) { - print('No $platform tests found for ${example.displayName}'); - } -} - -// The type for a function that takes a plugin directory and runs its native -// tests for a specific platform. -typedef _TestFunction = Future<_PlatformResult> Function( - RepositoryPackage, _TestMode); - -/// A collection of information related to a specific platform. -class _PlatformDetails { - const _PlatformDetails( - this.label, - this.testFunction, - ); - - /// The name to use in output. - final String label; - - /// The function to call to run tests. - final _TestFunction testFunction; -} - -/// Enabled state for different test types. -class _TestMode { - const _TestMode({required this.unit, required this.integration}); - - final bool unit; - final bool integration; - - bool get integrationOnly => integration && !unit; - bool get unitOnly => unit && !integration; -} - -/// The result of running a single platform's tests. -class _PlatformResult { - _PlatformResult(this.state, {this.error}); - - /// The overall state of the platform's tests. This should be: - /// - failed if any tests failed. - /// - succeeded if at least one test ran, and all tests passed. - /// - skipped if no tests ran. - final RunState state; - - /// An optional error string to include in the summary for this platform. - /// - /// Ignored unless [state] is `failed`. - final String? error; -} diff --git a/script/tool/lib/src/podspec_check_command.dart b/script/tool/lib/src/podspec_check_command.dart deleted file mode 100644 index 4cda7210a8ef..000000000000 --- a/script/tool/lib/src/podspec_check_command.dart +++ /dev/null @@ -1,194 +0,0 @@ -// Copyright 2013 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. - -import 'dart:convert'; -import 'dart:io'; - -import 'package:file/file.dart'; -import 'package:platform/platform.dart'; - -import 'common/core.dart'; -import 'common/package_looping_command.dart'; -import 'common/process_runner.dart'; -import 'common/repository_package.dart'; - -const int _exitUnsupportedPlatform = 2; -const int _exitPodNotInstalled = 3; - -/// Lint the CocoaPod podspecs and run unit tests. -/// -/// See https://guides.cocoapods.org/terminal/commands.html#pod_lib_lint. -class PodspecCheckCommand extends PackageLoopingCommand { - /// Creates an instance of the linter command. - PodspecCheckCommand( - Directory packagesDir, { - ProcessRunner processRunner = const ProcessRunner(), - Platform platform = const LocalPlatform(), - }) : super(packagesDir, processRunner: processRunner, platform: platform); - - @override - final String name = 'podspec-check'; - - @override - List get aliases => ['podspec', 'podspecs']; - - @override - final String description = - 'Runs "pod lib lint" on all iOS and macOS plugin podspecs, as well as ' - 'making sure the podspecs follow repository standards.\n\n' - 'This command requires "pod" and "flutter" to be in your path. Runs on macOS only.'; - - @override - Future initializeRun() async { - if (!platform.isMacOS) { - printError('This command is only supported on macOS'); - throw ToolExit(_exitUnsupportedPlatform); - } - - final ProcessResult result = await processRunner.run( - 'which', - ['pod'], - workingDir: packagesDir, - logOnError: true, - ); - if (result.exitCode != 0) { - printError('Unable to find "pod". Make sure it is in your path.'); - throw ToolExit(_exitPodNotInstalled); - } - } - - @override - Future runForPackage(RepositoryPackage package) async { - final List errors = []; - - final List podspecs = await _podspecsToLint(package); - if (podspecs.isEmpty) { - return PackageResult.skip('No podspecs.'); - } - - for (final File podspec in podspecs) { - if (!await _lintPodspec(podspec)) { - errors.add(podspec.basename); - } - } - - if (await _hasIOSSwiftCode(package)) { - print('iOS Swift code found, checking for search paths settings...'); - for (final File podspec in podspecs) { - if (_isPodspecMissingSearchPaths(podspec)) { - const String workaroundBlock = r''' - s.xcconfig = { - 'LIBRARY_SEARCH_PATHS' => '$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)/ $(SDKROOT)/usr/lib/swift', - 'LD_RUNPATH_SEARCH_PATHS' => '/usr/lib/swift', - } -'''; - final String path = - getRelativePosixPath(podspec, from: package.directory); - printError('$path is missing seach path configuration. Any iOS ' - 'plugin implementation that contains Swift implementation code ' - 'needs to contain the following:\n\n' - '$workaroundBlock\n' - 'For more details, see https://github.com/flutter/flutter/issues/118418.'); - errors.add(podspec.basename); - } - } - } - - return errors.isEmpty - ? PackageResult.success() - : PackageResult.fail(errors); - } - - Future> _podspecsToLint(RepositoryPackage package) async { - final List podspecs = - await getFilesForPackage(package).where((File entity) { - final String filePath = entity.path; - return path.extension(filePath) == '.podspec'; - }).toList(); - - podspecs.sort((File a, File b) => a.basename.compareTo(b.basename)); - return podspecs; - } - - Future _lintPodspec(File podspec) async { - // Do not run the static analyzer on plugins with known analyzer issues. - final String podspecPath = podspec.path; - - final String podspecBasename = podspec.basename; - print('Linting $podspecBasename'); - - // Lint plugin as framework (use_frameworks!). - final ProcessResult frameworkResult = - await _runPodLint(podspecPath, libraryLint: true); - print(frameworkResult.stdout); - print(frameworkResult.stderr); - - // Lint plugin as library. - final ProcessResult libraryResult = - await _runPodLint(podspecPath, libraryLint: false); - print(libraryResult.stdout); - print(libraryResult.stderr); - - return frameworkResult.exitCode == 0 && libraryResult.exitCode == 0; - } - - Future _runPodLint(String podspecPath, - {required bool libraryLint}) async { - final List arguments = [ - 'lib', - 'lint', - podspecPath, - '--configuration=Debug', // Release targets unsupported arm64 simulators. Use Debug to only build against targeted x86_64 simulator devices. - '--skip-tests', - '--use-modular-headers', // Flutter sets use_modular_headers! in its templates. - if (libraryLint) '--use-libraries' - ]; - - print('Running "pod ${arguments.join(' ')}"'); - return processRunner.run('pod', arguments, - workingDir: packagesDir, stdoutEncoding: utf8, stderrEncoding: utf8); - } - - /// Returns true if there is any iOS plugin implementation code written in - /// Swift. - Future _hasIOSSwiftCode(RepositoryPackage package) async { - return getFilesForPackage(package).any((File entity) { - final String relativePath = - getRelativePosixPath(entity, from: package.directory); - // Ignore example code. - if (relativePath.startsWith('example/')) { - return false; - } - final String filePath = entity.path; - return path.extension(filePath) == '.swift'; - }); - } - - /// Returns true if [podspec] could apply to iOS, but does not have the - /// workaround for search paths that makes Swift plugins build correctly in - /// Objective-C applications. See - /// https://github.com/flutter/flutter/issues/118418 for context and details. - /// - /// This does not check that the plugin has Swift code, and thus whether the - /// workaround is needed, only whether or not it is there. - bool _isPodspecMissingSearchPaths(File podspec) { - final String directory = podspec.parent.basename; - // All macOS Flutter apps are Swift, so macOS-only podspecs don't need the - // workaround. If it's anywhere other than macos/, err or the side of - // assuming it's required. - if (directory == 'macos') { - return false; - } - - // This errs on the side of being too strict, to minimize the chance of - // accidental incorrect configuration. If we ever need more flexibility - // due to a false negative we can adjust this as necessary. - final RegExp workaround = RegExp(r''' -\s*s\.(?:ios\.)?xcconfig = {[^}]* -\s*'LIBRARY_SEARCH_PATHS' => '\$\(TOOLCHAIN_DIR\)/usr/lib/swift/\$\(PLATFORM_NAME\)/ \$\(SDKROOT\)/usr/lib/swift', -\s*'LD_RUNPATH_SEARCH_PATHS' => '/usr/lib/swift',[^}]* -\s*}''', dotAll: true); - return !workaround.hasMatch(podspec.readAsStringSync()); - } -} diff --git a/script/tool/lib/src/publish_check_command.dart b/script/tool/lib/src/publish_check_command.dart deleted file mode 100644 index 14b240dc04c2..000000000000 --- a/script/tool/lib/src/publish_check_command.dart +++ /dev/null @@ -1,289 +0,0 @@ -// Copyright 2013 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. - -import 'dart:async'; -import 'dart:convert'; -import 'dart:io' as io; - -import 'package:file/file.dart'; -import 'package:http/http.dart' as http; -import 'package:platform/platform.dart'; -import 'package:pub_semver/pub_semver.dart'; - -import 'common/core.dart'; -import 'common/package_looping_command.dart'; -import 'common/process_runner.dart'; -import 'common/pub_version_finder.dart'; -import 'common/repository_package.dart'; - -/// A command to check that packages are publishable via 'dart publish'. -class PublishCheckCommand extends PackageLoopingCommand { - /// Creates an instance of the publish command. - PublishCheckCommand( - Directory packagesDir, { - ProcessRunner processRunner = const ProcessRunner(), - Platform platform = const LocalPlatform(), - http.Client? httpClient, - }) : _pubVersionFinder = - PubVersionFinder(httpClient: httpClient ?? http.Client()), - super(packagesDir, processRunner: processRunner, platform: platform) { - argParser.addFlag( - _allowPrereleaseFlag, - help: 'Allows the pre-release SDK warning to pass.\n' - 'When enabled, a pub warning, which asks to publish the package as a pre-release version when ' - 'the SDK constraint is a pre-release version, is ignored.', - ); - argParser.addFlag(_machineFlag, - help: 'Switch outputs to a machine readable JSON. \n' - 'The JSON contains a "status" field indicating the final status of the command, the possible values are:\n' - ' $_statusNeedsPublish: There is at least one package need to be published. They also passed all publish checks.\n' - ' $_statusMessageNoPublish: There are no packages needs to be published. Either no pubspec change detected or all versions have already been published.\n' - ' $_statusMessageError: Some error has occurred.'); - } - - static const String _allowPrereleaseFlag = 'allow-pre-release'; - static const String _machineFlag = 'machine'; - static const String _statusNeedsPublish = 'needs-publish'; - static const String _statusMessageNoPublish = 'no-publish'; - static const String _statusMessageError = 'error'; - static const String _statusKey = 'status'; - static const String _humanMessageKey = 'humanMessage'; - - @override - final String name = 'publish-check'; - - @override - final String description = - 'Checks to make sure that a package *could* be published.'; - - final PubVersionFinder _pubVersionFinder; - - /// The overall result of the run for machine-readable output. This is the - /// highest value that occurs during the run. - _PublishCheckResult _overallResult = _PublishCheckResult.nothingToPublish; - - @override - bool get captureOutput => getBoolArg(_machineFlag); - - @override - Future initializeRun() async { - _overallResult = _PublishCheckResult.nothingToPublish; - } - - @override - Future runForPackage(RepositoryPackage package) async { - _PublishCheckResult? result = await _passesPublishCheck(package); - if (result == null) { - return PackageResult.skip('Package is marked as unpublishable.'); - } - if (!_passesAuthorsCheck(package)) { - _printImportantStatusMessage( - 'No AUTHORS file found. Packages must include an AUTHORS file.', - isError: true); - result = _PublishCheckResult.error; - } - - if (result.index > _overallResult.index) { - _overallResult = result; - } - return result == _PublishCheckResult.error - ? PackageResult.fail() - : PackageResult.success(); - } - - @override - Future completeRun() async { - _pubVersionFinder.httpClient.close(); - } - - @override - Future handleCapturedOutput(List output) async { - final Map machineOutput = { - _statusKey: _statusStringForResult(_overallResult), - _humanMessageKey: output, - }; - - print(const JsonEncoder.withIndent(' ').convert(machineOutput)); - } - - String _statusStringForResult(_PublishCheckResult result) { - switch (result) { - case _PublishCheckResult.nothingToPublish: - return _statusMessageNoPublish; - case _PublishCheckResult.needsPublishing: - return _statusNeedsPublish; - case _PublishCheckResult.error: - return _statusMessageError; - } - } - - Pubspec? _tryParsePubspec(RepositoryPackage package) { - try { - return package.parsePubspec(); - } on Exception catch (exception) { - print( - 'Failed to parse `pubspec.yaml` at ${package.pubspecFile.path}: ' - '$exception', - ); - return null; - } - } - - // Run `dart pub get` on the examples of [package]. - Future _fetchExampleDeps(RepositoryPackage package) async { - for (final RepositoryPackage example in package.getExamples()) { - await processRunner.runAndStream( - 'dart', - ['pub', 'get'], - workingDir: example.directory, - ); - } - } - - Future _hasValidPublishCheckRun(RepositoryPackage package) async { - // `pub publish` does not do `dart pub get` inside `example` directories - // of a package (but they're part of the analysis output!). - // Issue: https://github.com/flutter/flutter/issues/113788 - await _fetchExampleDeps(package); - - print('Running pub publish --dry-run:'); - final io.Process process = await processRunner.start( - flutterCommand, - ['pub', 'publish', '--', '--dry-run'], - workingDirectory: package.directory, - ); - - final StringBuffer outputBuffer = StringBuffer(); - - final Completer stdOutCompleter = Completer(); - process.stdout.listen( - (List event) { - final String output = String.fromCharCodes(event); - if (output.isNotEmpty) { - print(output); - outputBuffer.write(output); - } - }, - onDone: () => stdOutCompleter.complete(), - ); - - final Completer stdInCompleter = Completer(); - process.stderr.listen( - (List event) { - final String output = String.fromCharCodes(event); - if (output.isNotEmpty) { - // The final result is always printed on stderr, whether success or - // failure. - final bool isError = !output.contains('has 0 warnings'); - _printImportantStatusMessage(output, isError: isError); - outputBuffer.write(output); - } - }, - onDone: () => stdInCompleter.complete(), - ); - - if (await process.exitCode == 0) { - return true; - } - - if (!getBoolArg(_allowPrereleaseFlag)) { - return false; - } - - await stdOutCompleter.future; - await stdInCompleter.future; - - final String output = outputBuffer.toString(); - return output.contains('Package has 1 warning') && - output.contains( - 'Packages with an SDK constraint on a pre-release of the Dart SDK should themselves be published as a pre-release version.'); - } - - /// Returns the result of the publish check, or null if the package is marked - /// as unpublishable. - Future<_PublishCheckResult?> _passesPublishCheck( - RepositoryPackage package) async { - final String packageName = package.directory.basename; - final Pubspec? pubspec = _tryParsePubspec(package); - if (pubspec == null) { - print('No valid pubspec found.'); - return _PublishCheckResult.error; - } else if (pubspec.publishTo == 'none') { - return null; - } - - final Version? version = pubspec.version; - final _PublishCheckResult alreadyPublishedResult = - await _checkPublishingStatus( - packageName: packageName, version: version); - if (alreadyPublishedResult == _PublishCheckResult.nothingToPublish) { - print( - 'Package $packageName version: $version has already be published on pub.'); - return alreadyPublishedResult; - } else if (alreadyPublishedResult == _PublishCheckResult.error) { - print('Check pub version failed $packageName'); - return _PublishCheckResult.error; - } - - if (await _hasValidPublishCheckRun(package)) { - print('Package $packageName is able to be published.'); - return _PublishCheckResult.needsPublishing; - } else { - print('Unable to publish $packageName'); - return _PublishCheckResult.error; - } - } - - // Check if `packageName` already has `version` published on pub. - Future<_PublishCheckResult> _checkPublishingStatus( - {required String packageName, required Version? version}) async { - final PubVersionFinderResponse pubVersionFinderResponse = - await _pubVersionFinder.getPackageVersion(packageName: packageName); - switch (pubVersionFinderResponse.result) { - case PubVersionFinderResult.success: - return pubVersionFinderResponse.versions.contains(version) - ? _PublishCheckResult.nothingToPublish - : _PublishCheckResult.needsPublishing; - case PubVersionFinderResult.fail: - print(''' -Error fetching version on pub for $packageName. -HTTP Status ${pubVersionFinderResponse.httpResponse.statusCode} -HTTP response: ${pubVersionFinderResponse.httpResponse.body} -'''); - return _PublishCheckResult.error; - case PubVersionFinderResult.noPackageFound: - return _PublishCheckResult.needsPublishing; - } - } - - bool _passesAuthorsCheck(RepositoryPackage package) { - final List pathComponents = - package.directory.fileSystem.path.split(package.path); - if (pathComponents.contains('third_party')) { - // Third-party packages aren't required to have an AUTHORS file. - return true; - } - return package.authorsFile.existsSync(); - } - - void _printImportantStatusMessage(String message, {required bool isError}) { - final String statusMessage = '${isError ? 'ERROR' : 'SUCCESS'}: $message'; - if (getBoolArg(_machineFlag)) { - print(statusMessage); - } else { - if (isError) { - printError(statusMessage); - } else { - printSuccess(statusMessage); - } - } - } -} - -/// Possible outcomes of of a publishing check. -enum _PublishCheckResult { - nothingToPublish, - needsPublishing, - error, -} diff --git a/script/tool/lib/src/publish_command.dart b/script/tool/lib/src/publish_command.dart deleted file mode 100644 index e7b3d110c5fa..000000000000 --- a/script/tool/lib/src/publish_command.dart +++ /dev/null @@ -1,456 +0,0 @@ -// Copyright 2013 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. - -import 'dart:async'; -import 'dart:convert'; -import 'dart:io' as io; - -import 'package:file/file.dart'; -import 'package:git/git.dart'; -import 'package:http/http.dart' as http; -import 'package:meta/meta.dart'; -import 'package:path/path.dart' as p; -import 'package:platform/platform.dart'; -import 'package:pub_semver/pub_semver.dart'; -import 'package:yaml/yaml.dart'; - -import 'common/core.dart'; -import 'common/file_utils.dart'; -import 'common/git_version_finder.dart'; -import 'common/package_command.dart'; -import 'common/package_looping_command.dart'; -import 'common/process_runner.dart'; -import 'common/pub_version_finder.dart'; -import 'common/repository_package.dart'; - -@immutable -class _RemoteInfo { - const _RemoteInfo({required this.name, required this.url}); - - /// The git name for the remote. - final String name; - - /// The remote's URL. - final String url; -} - -/// Wraps pub publish with a few niceties used by the flutter/plugin team. -/// -/// 1. Checks for any modified files in git and refuses to publish if there's an -/// issue. -/// 2. Tags the release with the format -v. -/// 3. Pushes the release to a remote. -/// -/// Both 2 and 3 are optional, see `plugin_tools help publish` for full -/// usage information. -/// -/// [processRunner], [print], and [stdin] can be overriden for easier testing. -class PublishCommand extends PackageLoopingCommand { - /// Creates an instance of the publish command. - PublishCommand( - Directory packagesDir, { - ProcessRunner processRunner = const ProcessRunner(), - Platform platform = const LocalPlatform(), - io.Stdin? stdinput, - GitDir? gitDir, - http.Client? httpClient, - }) : _pubVersionFinder = - PubVersionFinder(httpClient: httpClient ?? http.Client()), - _stdin = stdinput ?? io.stdin, - super(packagesDir, - platform: platform, processRunner: processRunner, gitDir: gitDir) { - argParser.addMultiOption(_pubFlagsOption, - help: - 'A list of options that will be forwarded on to pub. Separate multiple flags with commas.'); - argParser.addOption( - _remoteOption, - help: 'The name of the remote to push the tags to.', - // Flutter convention is to use "upstream" for the single source of truth, and "origin" for personal forks. - defaultsTo: 'upstream', - ); - argParser.addFlag( - _allChangedFlag, - help: - 'Release all packages that contains pubspec changes at the current commit compares to the base-sha.\n' - 'The --packages option is ignored if this is on.', - ); - argParser.addFlag( - _dryRunFlag, - help: - 'Skips the real `pub publish` and `git tag` commands and assumes both commands are successful.\n' - 'This does not run `pub publish --dry-run`.\n' - 'If you want to run the command with `pub publish --dry-run`, use `pub-publish-flags=--dry-run`', - ); - argParser.addFlag(_skipConfirmationFlag, - help: 'Run the command without asking for Y/N inputs.\n' - 'This command will add a `--force` flag to the `pub publish` command if it is not added with $_pubFlagsOption\n'); - } - - static const String _pubFlagsOption = 'pub-publish-flags'; - static const String _remoteOption = 'remote'; - static const String _allChangedFlag = 'all-changed'; - static const String _dryRunFlag = 'dry-run'; - static const String _skipConfirmationFlag = 'skip-confirmation'; - - static const String _pubCredentialName = 'PUB_CREDENTIALS'; - - // Version tags should follow -v. For example, - // `flutter_plugin_tools-v0.0.24`. - static const String _tagFormat = '%PACKAGE%-v%VERSION%'; - - @override - final String name = 'publish'; - - @override - final String description = - 'Attempts to publish the given packages and tag the release(s) on GitHub.\n' - 'If running this on CI, an environment variable named $_pubCredentialName must be set to a String that represents the pub credential JSON.\n' - 'WARNING: Do not check in the content of pub credential JSON, it should only come from secure sources.'; - - final io.Stdin _stdin; - StreamSubscription? _stdinSubscription; - final PubVersionFinder _pubVersionFinder; - - // Tags that already exist in the repository. - List _existingGitTags = []; - // The remote to push tags to. - late _RemoteInfo _remote; - // Flags to pass to `pub publish`. - late List _publishFlags; - - @override - String get successSummaryMessage => 'published'; - - @override - String get failureListHeader => - 'The following packages had failures during publishing:'; - - @override - Future initializeRun() async { - print('Checking local repo...'); - - // Ensure that the requested remote is present. - final String remoteName = getStringArg(_remoteOption); - final String? remoteUrl = await _verifyRemote(remoteName); - if (remoteUrl == null) { - printError('Unable to find URL for remote $remoteName; cannot push tags'); - throw ToolExit(1); - } - _remote = _RemoteInfo(name: remoteName, url: remoteUrl); - - // Pre-fetch all the repository's tags, to check against when publishing. - final GitDir repository = await gitDir; - final io.ProcessResult existingTagsResult = - await repository.runCommand(['tag', '--sort=-committerdate']); - _existingGitTags = (existingTagsResult.stdout as String).split('\n') - ..removeWhere((String element) => element.isEmpty); - - _publishFlags = [ - ...getStringListArg(_pubFlagsOption), - if (getBoolArg(_skipConfirmationFlag)) '--force', - ]; - - if (getBoolArg(_dryRunFlag)) { - print('=============== DRY RUN ==============='); - } - } - - @override - Stream getPackagesToProcess() async* { - if (getBoolArg(_allChangedFlag)) { - final GitVersionFinder gitVersionFinder = await retrieveVersionFinder(); - final String baseSha = await gitVersionFinder.getBaseSha(); - print( - 'Publishing all packages that have changed relative to "$baseSha"\n'); - final List changedPubspecs = - await gitVersionFinder.getChangedPubSpecs(); - - for (final String pubspecPath in changedPubspecs) { - // git outputs a relativa, Posix-style path. - final File pubspecFile = childFileWithSubcomponents( - packagesDir.fileSystem.directory((await gitDir).path), - p.posix.split(pubspecPath)); - yield PackageEnumerationEntry(RepositoryPackage(pubspecFile.parent), - excluded: false); - } - } else { - yield* getTargetPackages(filterExcluded: false); - } - } - - @override - Future runForPackage(RepositoryPackage package) async { - final PackageResult? checkResult = await _checkNeedsRelease(package); - if (checkResult != null) { - return checkResult; - } - - if (!await _checkGitStatus(package)) { - return PackageResult.fail(['uncommitted changes']); - } - - if (!await _publish(package)) { - return PackageResult.fail(['publish failed']); - } - - if (!await _tagRelease(package)) { - return PackageResult.fail(['tagging failed']); - } - - print('\nPublished ${package.directory.basename} successfully!'); - return PackageResult.success(); - } - - @override - Future completeRun() async { - _pubVersionFinder.httpClient.close(); - await _stdinSubscription?.cancel(); - _stdinSubscription = null; - } - - /// Checks whether [package] needs to be released, printing check status and - /// returning one of: - /// - PackageResult.fail if the check could not be completed - /// - PackageResult.skip if no release is necessary - /// - null if releasing should proceed - /// - /// In cases where a non-null result is returned, that should be returned - /// as the final result for the package, without further processing. - Future _checkNeedsRelease(RepositoryPackage package) async { - if (!package.pubspecFile.existsSync()) { - logWarning(''' -The pubspec file for ${package.displayName} does not exist, so no publishing will happen. -Safe to ignore if the package is deleted in this commit. -'''); - return PackageResult.skip('package deleted'); - } - - final Pubspec pubspec = package.parsePubspec(); - - if (pubspec.name == 'flutter_plugin_tools') { - // Ignore flutter_plugin_tools package when running publishing through flutter_plugin_tools. - // TODO(cyanglaz): Make the tool also auto publish flutter_plugin_tools package. - // https://github.com/flutter/flutter/issues/85430 - return PackageResult.skip( - 'publishing flutter_plugin_tools via the tool is not supported'); - } - - if (pubspec.publishTo == 'none') { - return PackageResult.skip('publish_to: none'); - } - - if (pubspec.version == null) { - printError( - 'No version found. A package that intentionally has no version should be marked "publish_to: none"'); - return PackageResult.fail(['no version']); - } - - // Check if the package named `packageName` with `version` has already - // been published. - final Version version = pubspec.version!; - final PubVersionFinderResponse pubVersionFinderResponse = - await _pubVersionFinder.getPackageVersion(packageName: pubspec.name); - if (pubVersionFinderResponse.versions.contains(version)) { - final String tagsForPackageWithSameVersion = _existingGitTags.firstWhere( - (String tag) => - tag.split('-v').first == pubspec.name && - tag.split('-v').last == version.toString(), - orElse: () => ''); - if (tagsForPackageWithSameVersion.isEmpty) { - printError( - '${pubspec.name} $version has already been published, however ' - 'the git release tag (${pubspec.name}-v$version) was not found. ' - 'Please manually fix the tag then run the command again.'); - return PackageResult.fail(['published but untagged']); - } else { - print('${pubspec.name} $version has already been published.'); - return PackageResult.skip('already published'); - } - } - return null; - } - - // Tag the release with -v, and push it to the remote. - // - // Return `true` if successful, `false` otherwise. - Future _tagRelease(RepositoryPackage package) async { - final String tag = _getTag(package); - print('Tagging release $tag...'); - if (!getBoolArg(_dryRunFlag)) { - final io.ProcessResult result = await (await gitDir).runCommand( - ['tag', tag], - throwOnError: false, - ); - if (result.exitCode != 0) { - return false; - } - } - - print('Pushing tag to ${_remote.name}...'); - final bool success = await _pushTagToRemote( - tag: tag, - remote: _remote, - ); - if (success) { - print('Release tagged!'); - } - return success; - } - - Future _checkGitStatus(RepositoryPackage package) async { - final io.ProcessResult statusResult = await (await gitDir).runCommand( - [ - 'status', - '--porcelain', - '--ignored', - package.directory.absolute.path - ], - throwOnError: false, - ); - if (statusResult.exitCode != 0) { - return false; - } - - final String statusOutput = statusResult.stdout as String; - if (statusOutput.isNotEmpty) { - printError( - "There are files in the package directory that haven't been saved in git. Refusing to publish these files:\n\n" - '$statusOutput\n' - 'If the directory should be clean, you can run `git clean -xdf && git reset --hard HEAD` to wipe all local changes.'); - } - return statusOutput.isEmpty; - } - - Future _verifyRemote(String remote) async { - final io.ProcessResult getRemoteUrlResult = await (await gitDir).runCommand( - ['remote', 'get-url', remote], - throwOnError: false, - ); - if (getRemoteUrlResult.exitCode != 0) { - return null; - } - return getRemoteUrlResult.stdout as String?; - } - - Future _publish(RepositoryPackage package) async { - print('Publishing...'); - print('Running `pub publish ${_publishFlags.join(' ')}` in ' - '${package.directory.absolute.path}...\n'); - if (getBoolArg(_dryRunFlag)) { - return true; - } - - if (_publishFlags.contains('--force')) { - _ensureValidPubCredential(); - } - - final io.Process publish = await processRunner.start( - flutterCommand, ['pub', 'publish', ..._publishFlags], - workingDirectory: package.directory); - publish.stdout.transform(utf8.decoder).listen((String data) => print(data)); - publish.stderr.transform(utf8.decoder).listen((String data) => print(data)); - _stdinSubscription ??= _stdin - .transform(utf8.decoder) - .listen((String data) => publish.stdin.writeln(data)); - final int result = await publish.exitCode; - if (result != 0) { - printError('Publishing ${package.directory.basename} failed.'); - return false; - } - - print('Package published!'); - return true; - } - - String _getTag(RepositoryPackage package) { - final File pubspecFile = package.pubspecFile; - final YamlMap pubspecYaml = - loadYaml(pubspecFile.readAsStringSync()) as YamlMap; - final String name = pubspecYaml['name'] as String; - final String version = pubspecYaml['version'] as String; - // We should have failed to publish if these were unset. - assert(name.isNotEmpty && version.isNotEmpty); - return _tagFormat - .replaceAll('%PACKAGE%', name) - .replaceAll('%VERSION%', version); - } - - // Pushes the `tag` to `remote` - // - // Return `true` if successful, `false` otherwise. - Future _pushTagToRemote({ - required String tag, - required _RemoteInfo remote, - }) async { - assert(remote != null && tag != null); - if (!getBoolArg(_dryRunFlag)) { - final io.ProcessResult result = await (await gitDir).runCommand( - ['push', remote.name, tag], - throwOnError: false, - ); - if (result.exitCode != 0) { - return false; - } - } - return true; - } - - void _ensureValidPubCredential() { - final String credentialsPath = _credentialsPath; - final File credentialFile = packagesDir.fileSystem.file(credentialsPath); - if (credentialFile.existsSync() && - credentialFile.readAsStringSync().isNotEmpty) { - return; - } - final String? credential = io.Platform.environment[_pubCredentialName]; - if (credential == null) { - printError(''' -No pub credential available. Please check if `$credentialsPath` is valid. -If running this command on CI, you can set the pub credential content in the $_pubCredentialName environment variable. -'''); - throw ToolExit(1); - } - credentialFile.openSync(mode: FileMode.writeOnlyAppend) - ..writeStringSync(credential) - ..closeSync(); - } - - /// Returns the correct path where the pub credential is stored. - @visibleForTesting - static String getCredentialPath() { - return _credentialsPath; - } -} - -/// The path in which pub expects to find its credentials file. -final String _credentialsPath = () { - // This follows the same logic as pub: - // https://github.com/dart-lang/pub/blob/d99b0d58f4059d7bb4ac4616fd3d54ec00a2b5d4/lib/src/system_cache.dart#L34-L43 - String? cacheDir; - final String? pubCache = io.Platform.environment['PUB_CACHE']; - if (pubCache != null) { - cacheDir = pubCache; - } else if (io.Platform.isWindows) { - final String? appData = io.Platform.environment['APPDATA']; - if (appData == null) { - printError('"APPDATA" environment variable is not set.'); - } else { - cacheDir = p.join(appData, 'Pub', 'Cache'); - } - } else { - final String? home = io.Platform.environment['HOME']; - if (home == null) { - printError('"HOME" environment variable is not set.'); - } else { - cacheDir = p.join(home, '.pub-cache'); - } - } - - if (cacheDir == null) { - printError('Unable to determine pub cache location'); - throw ToolExit(1); - } - - return p.join(cacheDir, 'credentials.json'); -}(); diff --git a/script/tool/lib/src/pubspec_check_command.dart b/script/tool/lib/src/pubspec_check_command.dart deleted file mode 100644 index aefa316a41f6..000000000000 --- a/script/tool/lib/src/pubspec_check_command.dart +++ /dev/null @@ -1,397 +0,0 @@ -// Copyright 2013 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. - -import 'package:file/file.dart'; -import 'package:git/git.dart'; -import 'package:platform/platform.dart'; -import 'package:pub_semver/pub_semver.dart'; -import 'package:yaml/yaml.dart'; - -import 'common/core.dart'; -import 'common/package_looping_command.dart'; -import 'common/process_runner.dart'; -import 'common/repository_package.dart'; - -/// A command to enforce pubspec conventions across the repository. -/// -/// This both ensures that repo best practices for which optional fields are -/// used are followed, and that the structure is consistent to make edits -/// across multiple pubspec files easier. -class PubspecCheckCommand extends PackageLoopingCommand { - /// Creates an instance of the version check command. - PubspecCheckCommand( - Directory packagesDir, { - ProcessRunner processRunner = const ProcessRunner(), - Platform platform = const LocalPlatform(), - GitDir? gitDir, - }) : super( - packagesDir, - processRunner: processRunner, - platform: platform, - gitDir: gitDir, - ) { - argParser.addOption( - _minMinDartVersionFlag, - help: - 'The minimum Dart version to allow as the minimum SDK constraint.\n\n' - 'This is only enforced for non-Flutter packages; Flutter packages ' - 'use --$_minMinFlutterVersionFlag', - ); - argParser.addOption( - _minMinFlutterVersionFlag, - help: - 'The minimum Flutter version to allow as the minimum SDK constraint.', - ); - } - - static const String _minMinDartVersionFlag = 'min-min-dart-version'; - static const String _minMinFlutterVersionFlag = 'min-min-flutter-version'; - - // Section order for plugins. Because the 'flutter' section is critical - // information for plugins, and usually small, it goes near the top unlike in - // a normal app or package. - static const List _majorPluginSections = [ - 'environment:', - 'flutter:', - 'dependencies:', - 'dev_dependencies:', - 'false_secrets:', - ]; - - static const List _majorPackageSections = [ - 'environment:', - 'dependencies:', - 'dev_dependencies:', - 'flutter:', - 'false_secrets:', - ]; - - static const String _expectedIssueLinkFormat = - 'https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A'; - - @override - final String name = 'pubspec-check'; - - @override - final String description = - 'Checks that pubspecs follow repository conventions.'; - - @override - bool get hasLongOutput => false; - - @override - PackageLoopingType get packageLoopingType => - PackageLoopingType.includeAllSubpackages; - - @override - Future runForPackage(RepositoryPackage package) async { - final File pubspec = package.pubspecFile; - final bool passesCheck = - !pubspec.existsSync() || await _checkPubspec(pubspec, package: package); - if (!passesCheck) { - return PackageResult.fail(); - } - return PackageResult.success(); - } - - Future _checkPubspec( - File pubspecFile, { - required RepositoryPackage package, - }) async { - final String contents = pubspecFile.readAsStringSync(); - final Pubspec? pubspec = _tryParsePubspec(contents); - if (pubspec == null) { - return false; - } - - final List pubspecLines = contents.split('\n'); - final bool isPlugin = pubspec.flutter?.containsKey('plugin') ?? false; - final List sectionOrder = - isPlugin ? _majorPluginSections : _majorPackageSections; - bool passing = _checkSectionOrder(pubspecLines, sectionOrder); - if (!passing) { - printError('${indentation}Major sections should follow standard ' - 'repository ordering:'); - final String listIndentation = indentation * 2; - printError('$listIndentation${sectionOrder.join('\n$listIndentation')}'); - } - - final String minMinDartVersionString = getStringArg(_minMinDartVersionFlag); - final String minMinFlutterVersionString = - getStringArg(_minMinFlutterVersionFlag); - final String? minVersionError = _checkForMinimumVersionError( - pubspec, - package, - minMinDartVersion: minMinDartVersionString.isEmpty - ? null - : Version.parse(minMinDartVersionString), - minMinFlutterVersion: minMinFlutterVersionString.isEmpty - ? null - : Version.parse(minMinFlutterVersionString), - ); - if (minVersionError != null) { - printError('$indentation$minVersionError'); - passing = false; - } - - if (isPlugin) { - final String? implementsError = - _checkForImplementsError(pubspec, package: package); - if (implementsError != null) { - printError('$indentation$implementsError'); - passing = false; - } - - final String? defaultPackageError = - _checkForDefaultPackageError(pubspec, package: package); - if (defaultPackageError != null) { - printError('$indentation$defaultPackageError'); - passing = false; - } - } - - // Ignore metadata that's only relevant for published packages if the - // packages is not intended for publishing. - if (pubspec.publishTo != 'none') { - final List repositoryErrors = - _checkForRepositoryLinkErrors(pubspec, package: package); - if (repositoryErrors.isNotEmpty) { - for (final String error in repositoryErrors) { - printError('$indentation$error'); - } - passing = false; - } - - if (!_checkIssueLink(pubspec)) { - printError( - '${indentation}A package should have an "issue_tracker" link to a ' - 'search for open flutter/flutter bugs with the relevant label:\n' - '${indentation * 2}$_expectedIssueLinkFormat'); - passing = false; - } - - // Don't check descriptions for federated package components other than - // the app-facing package, since they are unlisted, and are expected to - // have short descriptions. - if (!package.isPlatformInterface && !package.isPlatformImplementation) { - final String? descriptionError = - _checkDescription(pubspec, package: package); - if (descriptionError != null) { - printError('$indentation$descriptionError'); - passing = false; - } - } - } - - return passing; - } - - Pubspec? _tryParsePubspec(String pubspecContents) { - try { - return Pubspec.parse(pubspecContents); - } on Exception catch (exception) { - print(' Cannot parse pubspec.yaml: $exception'); - } - return null; - } - - bool _checkSectionOrder( - List pubspecLines, List sectionOrder) { - int previousSectionIndex = 0; - for (final String line in pubspecLines) { - final int index = sectionOrder.indexOf(line); - if (index == -1) { - continue; - } - if (index < previousSectionIndex) { - return false; - } - previousSectionIndex = index; - } - return true; - } - - List _checkForRepositoryLinkErrors( - Pubspec pubspec, { - required RepositoryPackage package, - }) { - final List errorMessages = []; - if (pubspec.repository == null) { - errorMessages.add('Missing "repository"'); - } else { - final String relativePackagePath = - getRelativePosixPath(package.directory, from: packagesDir.parent); - if (!pubspec.repository!.path.endsWith(relativePackagePath)) { - errorMessages - .add('The "repository" link should end with the package path.'); - } - - if (pubspec.repository!.path.contains('/master/')) { - errorMessages - .add('The "repository" link should use "main", not "master".'); - } - } - - if (pubspec.homepage != null) { - errorMessages - .add('Found a "homepage" entry; only "repository" should be used.'); - } - - return errorMessages; - } - - // Validates the "description" field for a package, returning an error - // string if there are any issues. - String? _checkDescription( - Pubspec pubspec, { - required RepositoryPackage package, - }) { - final String? description = pubspec.description; - if (description == null) { - return 'Missing "description"'; - } - - if (description.length < 60) { - return '"description" is too short. pub.dev recommends package ' - 'descriptions of 60-180 characters.'; - } - if (description.length > 180) { - return '"description" is too long. pub.dev recommends package ' - 'descriptions of 60-180 characters.'; - } - return null; - } - - bool _checkIssueLink(Pubspec pubspec) { - return pubspec.issueTracker - ?.toString() - .startsWith(_expectedIssueLinkFormat) ?? - false; - } - - // Validates the "implements" keyword for a plugin, returning an error - // string if there are any issues. - // - // Should only be called on plugin packages. - String? _checkForImplementsError( - Pubspec pubspec, { - required RepositoryPackage package, - }) { - if (_isImplementationPackage(package)) { - final YamlMap pluginSection = pubspec.flutter!['plugin'] as YamlMap; - final String? implements = pluginSection['implements'] as String?; - final String expectedImplements = package.directory.parent.basename; - if (implements == null) { - return 'Missing "implements: $expectedImplements" in "plugin" section.'; - } else if (implements != expectedImplements) { - return 'Expecetd "implements: $expectedImplements"; ' - 'found "implements: $implements".'; - } - } - return null; - } - - // Validates any "default_package" entries a plugin, returning an error - // string if there are any issues. - // - // Should only be called on plugin packages. - String? _checkForDefaultPackageError( - Pubspec pubspec, { - required RepositoryPackage package, - }) { - final YamlMap pluginSection = pubspec.flutter!['plugin'] as YamlMap; - final YamlMap? platforms = pluginSection['platforms'] as YamlMap?; - if (platforms == null) { - logWarning('Does not implement any platforms'); - return null; - } - final String packageName = package.directory.basename; - - // Validate that the default_package entries look correct (e.g., no typos). - final Set defaultPackages = {}; - for (final MapEntry platformEntry in platforms.entries) { - final YamlMap platformDetails = platformEntry.value! as YamlMap; - final String? defaultPackage = - platformDetails['default_package'] as String?; - if (defaultPackage != null) { - defaultPackages.add(defaultPackage); - if (!defaultPackage.startsWith('${packageName}_')) { - return '"$defaultPackage" is not an expected implementation name ' - 'for "$packageName"'; - } - } - } - - // Validate that all default_packages are also dependencies. - final Iterable dependencies = pubspec.dependencies.keys; - final Iterable missingPackages = defaultPackages - .where((String package) => !dependencies.contains(package)); - if (missingPackages.isNotEmpty) { - return 'The following default_packages are missing ' - 'corresponding dependencies:\n' - ' ${missingPackages.join('\n ')}'; - } - - return null; - } - - // Returns true if [packageName] appears to be an implementation package - // according to repository conventions. - bool _isImplementationPackage(RepositoryPackage package) { - if (!package.isFederated) { - return false; - } - final String packageName = package.directory.basename; - final String parentName = package.directory.parent.basename; - // A few known package names are not implementation packages; assume - // anything else is. (This is done instead of listing known implementation - // suffixes to allow for non-standard suffixes; e.g., to put several - // platforms in one package for code-sharing purposes.) - const Set nonImplementationSuffixes = { - '', // App-facing package. - '_platform_interface', // Platform interface package. - }; - final String suffix = packageName.substring(parentName.length); - return !nonImplementationSuffixes.contains(suffix); - } - - /// Validates that a Flutter package has a minimum SDK version constraint of - /// at least [minMinFlutterVersion] (if provided), or that a non-Flutter - /// package has a minimum SDK version constraint of [minMinDartVersion] - /// (if provided). - /// - /// Returns an error string if validation fails. - String? _checkForMinimumVersionError( - Pubspec pubspec, - RepositoryPackage package, { - Version? minMinDartVersion, - Version? minMinFlutterVersion, - }) { - final VersionConstraint? dartConstraint = pubspec.environment?['sdk']; - final VersionConstraint? flutterConstraint = - pubspec.environment?['flutter']; - - if (flutterConstraint != null) { - // Validate Flutter packages against the Flutter requirement. - if (minMinFlutterVersion != null) { - final Version? constraintMin = - flutterConstraint is VersionRange ? flutterConstraint.min : null; - if ((constraintMin ?? Version(0, 0, 0)) < minMinFlutterVersion) { - return 'Minimum allowed Flutter version $constraintMin is less than $minMinFlutterVersion'; - } - } - } else { - // Validate non-Flutter packages against the Dart requirement. - if (minMinDartVersion != null) { - final Version? constraintMin = - dartConstraint is VersionRange ? dartConstraint.min : null; - if ((constraintMin ?? Version(0, 0, 0)) < minMinDartVersion) { - return 'Minimum allowed Dart version $constraintMin is less than $minMinDartVersion'; - } - } - } - - return null; - } -} diff --git a/script/tool/lib/src/readme_check_command.dart b/script/tool/lib/src/readme_check_command.dart deleted file mode 100644 index cbbb8b835a13..000000000000 --- a/script/tool/lib/src/readme_check_command.dart +++ /dev/null @@ -1,344 +0,0 @@ -// Copyright 2013 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. - -import 'package:file/file.dart'; -import 'package:git/git.dart'; -import 'package:platform/platform.dart'; -import 'package:yaml/yaml.dart'; - -import 'common/core.dart'; -import 'common/package_looping_command.dart'; -import 'common/process_runner.dart'; -import 'common/repository_package.dart'; - -const String _instructionWikiUrl = - 'https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages'; - -/// A command to enforce README conventions across the repository. -class ReadmeCheckCommand extends PackageLoopingCommand { - /// Creates an instance of the README check command. - ReadmeCheckCommand( - Directory packagesDir, { - ProcessRunner processRunner = const ProcessRunner(), - Platform platform = const LocalPlatform(), - GitDir? gitDir, - }) : super( - packagesDir, - processRunner: processRunner, - platform: platform, - gitDir: gitDir, - ) { - argParser.addFlag(_requireExcerptsArg, - help: 'Require that Dart code blocks be managed by code-excerpt.'); - } - - static const String _requireExcerptsArg = 'require-excerpts'; - - // Standardized capitalizations for platforms that a plugin can support. - static const Map _standardPlatformNames = { - 'android': 'Android', - 'ios': 'iOS', - 'linux': 'Linux', - 'macos': 'macOS', - 'web': 'Web', - 'windows': 'Windows', - }; - - @override - final String name = 'readme-check'; - - @override - final String description = - 'Checks that READMEs follow repository conventions.'; - - @override - bool get hasLongOutput => false; - - @override - Future runForPackage(RepositoryPackage package) async { - final List errors = _validateReadme(package.readmeFile, - mainPackage: package, isExample: false); - for (final RepositoryPackage packageToCheck in package.getExamples()) { - errors.addAll(_validateReadme(packageToCheck.readmeFile, - mainPackage: package, isExample: true)); - } - - // If there's an example/README.md for a multi-example package, validate - // that as well, as it will be shown on pub.dev. - final Directory exampleDir = package.directory.childDirectory('example'); - final File exampleDirReadme = exampleDir.childFile('README.md'); - if (exampleDir.existsSync() && !isPackage(exampleDir)) { - errors.addAll(_validateReadme(exampleDirReadme, - mainPackage: package, isExample: true)); - } - - return errors.isEmpty - ? PackageResult.success() - : PackageResult.fail(errors); - } - - List _validateReadme(File readme, - {required RepositoryPackage mainPackage, required bool isExample}) { - if (!readme.existsSync()) { - if (isExample) { - print('${indentation}No README for ' - '${getRelativePosixPath(readme.parent, from: mainPackage.directory)}'); - return []; - } else { - printError('${indentation}No README found at ' - '${getRelativePosixPath(readme, from: mainPackage.directory)}'); - return ['Missing README.md']; - } - } - - print('${indentation}Checking ' - '${getRelativePosixPath(readme, from: mainPackage.directory)}...'); - - final List readmeLines = readme.readAsLinesSync(); - final List errors = []; - - final String? blockValidationError = - _validateCodeBlocks(readmeLines, mainPackage: mainPackage); - if (blockValidationError != null) { - errors.add(blockValidationError); - } - - errors.addAll(_validateBoilerplate(readmeLines, - mainPackage: mainPackage, isExample: isExample)); - - // Check if this is the main readme for a plugin, and if so enforce extra - // checks. - if (!isExample) { - final Pubspec pubspec = mainPackage.parsePubspec(); - final bool isPlugin = pubspec.flutter?['plugin'] != null; - if (isPlugin && (!mainPackage.isFederated || mainPackage.isAppFacing)) { - final String? error = _validateSupportedPlatforms(readmeLines, pubspec); - if (error != null) { - errors.add(error); - } - } - } - - return errors; - } - - /// Validates that code blocks (``` ... ```) follow repository standards. - String? _validateCodeBlocks( - List readmeLines, { - required RepositoryPackage mainPackage, - }) { - final RegExp codeBlockDelimiterPattern = RegExp(r'^\s*```\s*([^ ]*)\s*'); - const String excerptTagStart = ' missingLanguageLines = []; - final List missingExcerptLines = []; - bool inBlock = false; - for (int i = 0; i < readmeLines.length; ++i) { - final RegExpMatch? match = - codeBlockDelimiterPattern.firstMatch(readmeLines[i]); - if (match == null) { - continue; - } - if (inBlock) { - inBlock = false; - continue; - } - inBlock = true; - - final int humanReadableLineNumber = i + 1; - - // Ensure that there's a language tag. - final String infoString = match[1] ?? ''; - if (infoString.isEmpty) { - missingLanguageLines.add(humanReadableLineNumber); - continue; - } - - // Check for code-excerpt usage if requested. - if (getBoolArg(_requireExcerptsArg) && infoString == 'dart') { - if (i == 0 || !readmeLines[i - 1].trim().startsWith(excerptTagStart)) { - missingExcerptLines.add(humanReadableLineNumber); - } - } - } - - String? errorSummary; - - if (missingLanguageLines.isNotEmpty) { - for (final int lineNumber in missingLanguageLines) { - printError('${indentation}Code block at line $lineNumber is missing ' - 'a language identifier.'); - } - printError( - '\n${indentation}For each block listed above, add a language tag to ' - 'the opening block. For instance, for Dart code, use:\n' - '${indentation * 2}```dart\n'); - errorSummary = 'Missing language identifier for code block'; - } - - // If any blocks use code excerpts, make sure excerpting is configured - // for the package. - if (readmeLines.any((String line) => line.startsWith(excerptTagStart))) { - const String buildRunnerConfigFile = 'build.excerpt.yaml'; - if (!mainPackage.getExamples().any((RepositoryPackage example) => - example.directory.childFile(buildRunnerConfigFile).existsSync())) { - printError('code-excerpt tag found, but the package is not configured ' - 'for excerpting. Follow the instructions at\n' - '$_instructionWikiUrl\n' - 'for setting up a build.excerpt.yaml file.'); - errorSummary ??= 'Missing code-excerpt configuration'; - } - } - - if (missingExcerptLines.isNotEmpty) { - for (final int lineNumber in missingExcerptLines) { - printError('${indentation}Dart code block at line $lineNumber is not ' - 'managed by code-excerpt.'); - } - printError( - '\n${indentation}For each block listed above, add ' - 'tag on the previous line, and ensure that a build.excerpt.yaml is ' - 'configured for the source example as explained at\n' - '$_instructionWikiUrl'); - errorSummary ??= 'Missing code-excerpt management for code block'; - } - - return errorSummary; - } - - /// Validates that the plugin has a supported platforms table following the - /// expected format, returning an error string if any issues are found. - String? _validateSupportedPlatforms( - List readmeLines, Pubspec pubspec) { - // Example table following expected format: - // | | Android | iOS | Web | - // |----------------|---------|----------|------------------------| - // | **Support** | SDK 21+ | iOS 10+* | [See `camera_web `][1] | - final int detailsLineNumber = readmeLines - .indexWhere((String line) => line.startsWith('| **Support**')); - if (detailsLineNumber == -1) { - return 'No OS support table found'; - } - final int osLineNumber = detailsLineNumber - 2; - if (osLineNumber < 0 || !readmeLines[osLineNumber].startsWith('|')) { - return 'OS support table does not have the expected header format'; - } - - // Utility method to convert an iterable of strings to a case-insensitive - // sorted, comma-separated string of its elements. - String sortedListString(Iterable entries) { - final List entryList = entries.toList(); - entryList.sort( - (String a, String b) => a.toLowerCase().compareTo(b.toLowerCase())); - return entryList.join(', '); - } - - // Validate that the supported OS lists match. - final YamlMap pluginSection = pubspec.flutter!['plugin'] as YamlMap; - final dynamic platformsEntry = pluginSection['platforms']; - if (platformsEntry == null) { - logWarning('Plugin not support any platforms'); - return null; - } - final YamlMap platformSupportMaps = platformsEntry as YamlMap; - final Set actuallySupportedPlatform = - platformSupportMaps.keys.toSet().cast(); - final Iterable documentedPlatforms = readmeLines[osLineNumber] - .split('|') - .map((String entry) => entry.trim()) - .where((String entry) => entry.isNotEmpty); - final Set documentedPlatformsLowercase = - documentedPlatforms.map((String entry) => entry.toLowerCase()).toSet(); - if (actuallySupportedPlatform.length != documentedPlatforms.length || - actuallySupportedPlatform - .intersection(documentedPlatformsLowercase) - .length != - actuallySupportedPlatform.length) { - printError(''' -${indentation}OS support table does not match supported platforms: -${indentation * 2}Actual: ${sortedListString(actuallySupportedPlatform)} -${indentation * 2}Documented: ${sortedListString(documentedPlatformsLowercase)} -'''); - return 'Incorrect OS support table'; - } - - // Enforce a standard set of capitalizations for the OS headings. - final Iterable incorrectCapitalizations = documentedPlatforms - .toSet() - .difference(_standardPlatformNames.values.toSet()); - if (incorrectCapitalizations.isNotEmpty) { - final Iterable expectedVersions = incorrectCapitalizations - .map((String name) => _standardPlatformNames[name.toLowerCase()]!); - printError(''' -${indentation}Incorrect OS capitalization: ${sortedListString(incorrectCapitalizations)} -${indentation * 2}Please use standard capitalizations: ${sortedListString(expectedVersions)} -'''); - return 'Incorrect OS support formatting'; - } - - // TODO(stuartmorgan): Add validation that the minimums in the table are - // consistent with what the current implementations require. See - // https://github.com/flutter/flutter/issues/84200 - return null; - } - - /// Validates [readmeLines], outputing error messages for any issue and - /// returning an array of error summaries (if any). - /// - /// Returns an empty array if validation passes. - List _validateBoilerplate( - List readmeLines, { - required RepositoryPackage mainPackage, - required bool isExample, - }) { - final List errors = []; - - if (_containsTemplateFlutterBoilerplate(readmeLines)) { - printError('${indentation}The boilerplate section about getting started ' - 'with Flutter should not be left in.'); - errors.add('Contains template boilerplate'); - } - - // Enforce a repository-standard message in implementation plugin examples, - // since they aren't typical examples, which has been a source of - // confusion for plugin clients who find them. - if (isExample && mainPackage.isPlatformImplementation) { - if (_containsExampleBoilerplate(readmeLines)) { - printError('${indentation}The boilerplate should not be left in for a ' - "federated plugin implementation package's example."); - errors.add('Contains template boilerplate'); - } - if (!_containsImplementationExampleExplanation(readmeLines)) { - printError('${indentation}The example README for a platform ' - 'implementation package should warn readers about its intended ' - 'use. Please copy the example README from another implementation ' - 'package in this repository.'); - errors.add('Missing implementation package example warning'); - } - } - - return errors; - } - - /// Returns true if the README still has unwanted parts of the boilerplate - /// from the `flutter create` templates. - bool _containsTemplateFlutterBoilerplate(List readmeLines) { - return readmeLines.any((String line) => - line.contains('For help getting started with Flutter')); - } - - /// Returns true if the README still has the generic description of an - /// example from the `flutter create` templates. - bool _containsExampleBoilerplate(List readmeLines) { - return readmeLines - .any((String line) => line.contains('Demonstrates how to use the')); - } - - /// Returns true if the README contains the repository-standard explanation of - /// the purpose of a federated plugin implementation's example. - bool _containsImplementationExampleExplanation(List readmeLines) { - return readmeLines.contains('# Platform Implementation Test App') && - readmeLines - .any((String line) => line.contains('This is a test app for')); - } -} diff --git a/script/tool/lib/src/remove_dev_dependencies.dart b/script/tool/lib/src/remove_dev_dependencies.dart deleted file mode 100644 index 3085e0df85e0..000000000000 --- a/script/tool/lib/src/remove_dev_dependencies.dart +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2013 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. - -import 'package:file/file.dart'; -import 'package:yaml/yaml.dart'; -import 'package:yaml_edit/yaml_edit.dart'; - -import 'common/package_looping_command.dart'; -import 'common/repository_package.dart'; - -/// A command to remove dev_dependencies, which are not used by package clients. -/// -/// This is intended for use with legacy Flutter version testing, to allow -/// running analysis (with --lib-only) with versions that are supported for -/// clients of the library, but not for development of the library. -class RemoveDevDependenciesCommand extends PackageLoopingCommand { - /// Creates a publish metadata updater command instance. - RemoveDevDependenciesCommand(Directory packagesDir) : super(packagesDir); - - @override - final String name = 'remove-dev-dependencies'; - - @override - final String description = 'Removes any dev_dependencies section from a ' - 'package, to allow more legacy testing.'; - - @override - bool get hasLongOutput => false; - - @override - PackageLoopingType get packageLoopingType => - PackageLoopingType.includeAllSubpackages; - - @override - Future runForPackage(RepositoryPackage package) async { - bool changed = false; - final YamlEditor editablePubspec = - YamlEditor(package.pubspecFile.readAsStringSync()); - const String devDependenciesKey = 'dev_dependencies'; - final YamlNode root = editablePubspec.parseAt([]); - final YamlMap? devDependencies = - (root as YamlMap)[devDependenciesKey] as YamlMap?; - if (devDependencies != null) { - changed = true; - print('${indentation}Removed dev_dependencies'); - editablePubspec.remove([devDependenciesKey]); - } - - if (changed) { - package.pubspecFile.writeAsStringSync(editablePubspec.toString()); - } - - return changed - ? PackageResult.success() - : PackageResult.skip('Nothing to remove.'); - } -} diff --git a/script/tool/lib/src/test_command.dart b/script/tool/lib/src/test_command.dart deleted file mode 100644 index 5101b8f19e7e..000000000000 --- a/script/tool/lib/src/test_command.dart +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright 2013 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. - -import 'package:file/file.dart'; -import 'package:platform/platform.dart'; - -import 'common/core.dart'; -import 'common/package_looping_command.dart'; -import 'common/plugin_utils.dart'; -import 'common/process_runner.dart'; -import 'common/repository_package.dart'; - -/// A command to run Dart unit tests for packages. -class TestCommand extends PackageLoopingCommand { - /// Creates an instance of the test command. - TestCommand( - Directory packagesDir, { - ProcessRunner processRunner = const ProcessRunner(), - Platform platform = const LocalPlatform(), - }) : super(packagesDir, processRunner: processRunner, platform: platform) { - argParser.addOption( - kEnableExperiment, - defaultsTo: '', - help: - 'Runs Dart unit tests in Dart VM with the given experiments enabled. ' - 'See https://github.com/dart-lang/sdk/blob/main/docs/process/experimental-flags.md ' - 'for details.', - ); - } - - @override - final String name = 'test'; - - @override - final String description = 'Runs the Dart tests for all packages.\n\n' - 'This command requires "flutter" to be in your path.'; - - @override - PackageLoopingType get packageLoopingType => - PackageLoopingType.includeAllSubpackages; - - @override - Future runForPackage(RepositoryPackage package) async { - if (!package.testDirectory.existsSync()) { - return PackageResult.skip('No test/ directory.'); - } - - bool passed; - if (package.requiresFlutter()) { - passed = await _runFlutterTests(package); - } else { - passed = await _runDartTests(package); - } - return passed ? PackageResult.success() : PackageResult.fail(); - } - - /// Runs the Dart tests for a Flutter package, returning true on success. - Future _runFlutterTests(RepositoryPackage package) async { - final String experiment = getStringArg(kEnableExperiment); - - final int exitCode = await processRunner.runAndStream( - flutterCommand, - [ - 'test', - '--color', - if (experiment.isNotEmpty) '--enable-experiment=$experiment', - // TODO(ditman): Remove this once all plugins are migrated to 'drive'. - if (pluginSupportsPlatform(platformWeb, package)) '--platform=chrome', - ], - workingDir: package.directory, - ); - return exitCode == 0; - } - - /// Runs the Dart tests for a non-Flutter package, returning true on success. - Future _runDartTests(RepositoryPackage package) async { - // Unlike `flutter test`, `pub run test` does not automatically get - // packages - int exitCode = await processRunner.runAndStream( - 'dart', - ['pub', 'get'], - workingDir: package.directory, - ); - if (exitCode != 0) { - printError('Unable to fetch dependencies.'); - return false; - } - - final String experiment = getStringArg(kEnableExperiment); - - exitCode = await processRunner.runAndStream( - 'dart', - [ - 'run', - if (experiment.isNotEmpty) '--enable-experiment=$experiment', - 'test', - ], - workingDir: package.directory, - ); - - return exitCode == 0; - } -} diff --git a/script/tool/lib/src/update_excerpts_command.dart b/script/tool/lib/src/update_excerpts_command.dart deleted file mode 100644 index e65bed846cbc..000000000000 --- a/script/tool/lib/src/update_excerpts_command.dart +++ /dev/null @@ -1,233 +0,0 @@ -// Copyright 2013 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. - -import 'dart:io' as io; - -import 'package:file/file.dart'; -import 'package:git/git.dart'; -import 'package:platform/platform.dart'; -import 'package:yaml/yaml.dart'; -import 'package:yaml_edit/yaml_edit.dart'; - -import 'common/core.dart'; -import 'common/package_looping_command.dart'; -import 'common/process_runner.dart'; -import 'common/repository_package.dart'; - -/// A command to update README code excerpts from code files. -class UpdateExcerptsCommand extends PackageLoopingCommand { - /// Creates a excerpt updater command instance. - UpdateExcerptsCommand( - Directory packagesDir, { - ProcessRunner processRunner = const ProcessRunner(), - Platform platform = const LocalPlatform(), - GitDir? gitDir, - }) : super( - packagesDir, - processRunner: processRunner, - platform: platform, - gitDir: gitDir, - ) { - argParser.addFlag(_failOnChangeFlag, hide: true); - } - - static const String _failOnChangeFlag = 'fail-on-change'; - - static const String _buildRunnerConfigName = 'excerpt'; - // The name of the build_runner configuration file that will be in an example - // directory if the package is set up to use `code-excerpt`. - static const String _buildRunnerConfigFile = - 'build.$_buildRunnerConfigName.yaml'; - - // The relative directory path to put the extracted excerpt yaml files. - static const String _excerptOutputDir = 'excerpts'; - - // The filename to store the pre-modification copy of the pubspec. - static const String _originalPubspecFilename = - 'pubspec.plugin_tools_original.yaml'; - - @override - final String name = 'update-excerpts'; - - @override - final String description = 'Updates code excerpts in README.md files, based ' - 'on code from code files, via code-excerpt'; - - @override - Future runForPackage(RepositoryPackage package) async { - final Iterable configuredExamples = package - .getExamples() - .where((RepositoryPackage example) => - example.directory.childFile(_buildRunnerConfigFile).existsSync()); - - if (configuredExamples.isEmpty) { - return PackageResult.skip( - 'No $_buildRunnerConfigFile found in example(s).'); - } - - final Directory repoRoot = - packagesDir.fileSystem.directory((await gitDir).path); - - for (final RepositoryPackage example in configuredExamples) { - _addSubmoduleDependencies(example, repoRoot: repoRoot); - - try { - // Ensure that dependencies are available. - final int pubGetExitCode = await processRunner.runAndStream( - 'dart', ['pub', 'get'], - workingDir: example.directory); - if (pubGetExitCode != 0) { - return PackageResult.fail( - ['Unable to get script dependencies']); - } - - // Update the excerpts. - if (!await _extractSnippets(example)) { - return PackageResult.fail(['Unable to extract excerpts']); - } - if (!await _injectSnippets(example, targetPackage: package)) { - return PackageResult.fail(['Unable to inject excerpts']); - } - } finally { - // Clean up the pubspec changes and extracted excerpts directory. - _undoPubspecChanges(example); - final Directory excerptDirectory = - example.directory.childDirectory(_excerptOutputDir); - if (excerptDirectory.existsSync()) { - excerptDirectory.deleteSync(recursive: true); - } - } - } - - if (getBoolArg(_failOnChangeFlag)) { - final String? stateError = await _validateRepositoryState(); - if (stateError != null) { - printError('README.md is out of sync with its source excerpts.\n\n' - 'If you edited code in README.md directly, you should instead edit ' - 'the example source files. If you edited source files, run the ' - 'repository tooling\'s "$name" command on this package, and update ' - 'your PR with the resulting changes.'); - return PackageResult.fail([stateError]); - } - } - - return PackageResult.success(); - } - - /// Runs the extraction step to create the excerpt files for the given - /// example, returning true on success. - Future _extractSnippets(RepositoryPackage example) async { - final int exitCode = await processRunner.runAndStream( - 'dart', - [ - 'run', - 'build_runner', - 'build', - '--config', - _buildRunnerConfigName, - '--output', - _excerptOutputDir, - '--delete-conflicting-outputs', - ], - workingDir: example.directory); - return exitCode == 0; - } - - /// Runs the injection step to update [targetPackage]'s README with the latest - /// excerpts from [example], returning true on success. - Future _injectSnippets( - RepositoryPackage example, { - required RepositoryPackage targetPackage, - }) async { - final String relativeReadmePath = - getRelativePosixPath(targetPackage.readmeFile, from: example.directory); - final int exitCode = await processRunner.runAndStream( - 'dart', - [ - 'run', - 'code_excerpt_updater', - '--write-in-place', - '--yaml', - '--no-escape-ng-interpolation', - relativeReadmePath, - ], - workingDir: example.directory); - return exitCode == 0; - } - - /// Adds `code_excerpter` and `code_excerpt_updater` to [package]'s - /// `dev_dependencies` using path-based references to the submodule copies. - /// - /// This is done on the fly rather than being checked in so that: - /// - Just building examples don't require everyone to check out submodules. - /// - Examples can be analyzed/built even on versions of Flutter that these - /// submodules do not support. - void _addSubmoduleDependencies(RepositoryPackage package, - {required Directory repoRoot}) { - final String pubspecContents = package.pubspecFile.readAsStringSync(); - // Save aside a copy of the current pubspec state. This allows restoration - // to the previous state regardless of its git status at the time the script - // ran. - package.directory - .childFile(_originalPubspecFilename) - .writeAsStringSync(pubspecContents); - - // Update the actual pubspec. - final YamlEditor editablePubspec = YamlEditor(pubspecContents); - const String devDependenciesKey = 'dev_dependencies'; - final YamlNode root = editablePubspec.parseAt([]); - // Ensure that there's a `dev_dependencies` entry to update. - if ((root as YamlMap)[devDependenciesKey] == null) { - editablePubspec.update(['dev_dependencies'], YamlMap()); - } - final Set submoduleDependencies = { - 'code_excerpter', - 'code_excerpt_updater', - }; - final String relativeRootPath = - getRelativePosixPath(repoRoot, from: package.directory); - for (final String dependency in submoduleDependencies) { - editablePubspec.update([ - devDependenciesKey, - dependency - ], { - 'path': '$relativeRootPath/site-shared/packages/$dependency' - }); - } - package.pubspecFile.writeAsStringSync(editablePubspec.toString()); - } - - /// Restores the version of the pubspec that was present before running - /// [_addSubmoduleDependencies]. - void _undoPubspecChanges(RepositoryPackage package) { - package.directory - .childFile(_originalPubspecFilename) - .renameSync(package.pubspecFile.path); - } - - /// Checks the git state, returning an error string if any .md files have - /// changed. - Future _validateRepositoryState() async { - final io.ProcessResult checkFiles = await processRunner.run( - 'git', - ['ls-files', '--modified'], - workingDir: packagesDir, - logOnError: true, - ); - if (checkFiles.exitCode != 0) { - return 'Unable to determine local file state'; - } - - final String stdout = checkFiles.stdout as String; - final List changedFiles = stdout.trim().split('\n'); - final Iterable changedMDFiles = - changedFiles.where((String filePath) => filePath.endsWith('.md')); - if (changedMDFiles.isNotEmpty) { - return 'Snippets are out of sync in the following files: ' - '${changedMDFiles.join(', ')}'; - } - - return null; - } -} diff --git a/script/tool/lib/src/update_release_info_command.dart b/script/tool/lib/src/update_release_info_command.dart deleted file mode 100644 index 240ae72eed71..000000000000 --- a/script/tool/lib/src/update_release_info_command.dart +++ /dev/null @@ -1,317 +0,0 @@ -// Copyright 2013 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. - -import 'package:args/command_runner.dart'; -import 'package:file/file.dart'; -import 'package:git/git.dart'; -import 'package:pub_semver/pub_semver.dart'; -import 'package:yaml_edit/yaml_edit.dart'; - -import 'common/core.dart'; -import 'common/git_version_finder.dart'; -import 'common/package_looping_command.dart'; -import 'common/package_state_utils.dart'; -import 'common/repository_package.dart'; - -/// Supported version change types, from smallest to largest component. -enum _VersionIncrementType { build, bugfix, minor } - -/// Possible results of attempting to update a CHANGELOG.md file. -enum _ChangelogUpdateOutcome { addedSection, updatedSection, failed } - -/// A state machine for the process of updating a CHANGELOG.md. -enum _ChangelogUpdateState { - /// Looking for the first version section. - findingFirstSection, - - /// Looking for the first list entry in an existing section. - findingFirstListItem, - - /// Finished with updates. - finishedUpdating, -} - -/// A command to update the changelog, and optionally version, of packages. -class UpdateReleaseInfoCommand extends PackageLoopingCommand { - /// Creates a publish metadata updater command instance. - UpdateReleaseInfoCommand( - Directory packagesDir, { - GitDir? gitDir, - }) : super(packagesDir, gitDir: gitDir) { - argParser.addOption(_changelogFlag, - mandatory: true, - help: 'The changelog entry to add. ' - 'Each line will be a separate list entry.'); - argParser.addOption(_versionTypeFlag, - mandatory: true, - help: 'The version change level', - allowed: [ - _versionNext, - _versionMinimal, - _versionBugfix, - _versionMinor, - ], - allowedHelp: { - _versionNext: - 'No version change; just adds a NEXT entry to the changelog.', - _versionBugfix: 'Increments the bugfix version.', - _versionMinor: 'Increments the minor version.', - _versionMinimal: 'Depending on the changes to each package: ' - 'increments the bugfix version (for publishable changes), ' - "uses NEXT (for changes that don't need to be published), " - 'or skips (if no changes).', - }); - } - - static const String _changelogFlag = 'changelog'; - static const String _versionTypeFlag = 'version'; - - static const String _versionNext = 'next'; - static const String _versionBugfix = 'bugfix'; - static const String _versionMinor = 'minor'; - static const String _versionMinimal = 'minimal'; - - // The version change type, if there is a set type for all platforms. - // - // If null, either there is no version change, or it is dynamic (`minimal`). - _VersionIncrementType? _versionChange; - - // The cache of changed files, for dynamic version change determination. - // - // Only set for `minimal` version change. - late final List _changedFiles; - - @override - final String name = 'update-release-info'; - - @override - final String description = 'Updates CHANGELOG.md files, and optionally the ' - 'version in pubspec.yaml, in a way that is consistent with version-check ' - 'enforcement.'; - - @override - bool get hasLongOutput => false; - - @override - Future initializeRun() async { - if (getStringArg(_changelogFlag).trim().isEmpty) { - throw UsageException('Changelog message must not be empty.', usage); - } - switch (getStringArg(_versionTypeFlag)) { - case _versionMinor: - _versionChange = _VersionIncrementType.minor; - break; - case _versionBugfix: - _versionChange = _VersionIncrementType.bugfix; - break; - case _versionMinimal: - final GitVersionFinder gitVersionFinder = await retrieveVersionFinder(); - // If the line below fails with "Not a valid object name FETCH_HEAD" - // run "git fetch", FETCH_HEAD is a temporary reference that only exists - // after a fetch. This can happen when a branch is made locally and - // pushed but never fetched. - _changedFiles = await gitVersionFinder.getChangedFiles(); - // Anothing other than a fixed change is null. - _versionChange = null; - break; - case _versionNext: - _versionChange = null; - break; - default: - throw UnimplementedError('Unimplemented version change type'); - } - } - - @override - Future runForPackage(RepositoryPackage package) async { - String nextVersionString; - - _VersionIncrementType? versionChange = _versionChange; - - // If the change type is `minimal` determine what changes, if any, are - // needed. - if (versionChange == null && - getStringArg(_versionTypeFlag) == _versionMinimal) { - final Directory gitRoot = - packagesDir.fileSystem.directory((await gitDir).path); - final String relativePackagePath = - getRelativePosixPath(package.directory, from: gitRoot); - final PackageChangeState state = await checkPackageChangeState(package, - changedPaths: _changedFiles, - relativePackagePath: relativePackagePath); - - if (!state.hasChanges) { - return PackageResult.skip('No changes to package'); - } - if (!state.needsVersionChange && !state.needsChangelogChange) { - return PackageResult.skip('No non-exempt changes to package'); - } - if (state.needsVersionChange) { - versionChange = _VersionIncrementType.bugfix; - } - } - - if (versionChange != null) { - final Version? updatedVersion = - _updatePubspecVersion(package, versionChange); - if (updatedVersion == null) { - return PackageResult.fail( - ['Could not determine current version.']); - } - nextVersionString = updatedVersion.toString(); - print('${indentation}Incremented version to $nextVersionString.'); - } else { - nextVersionString = 'NEXT'; - } - - final _ChangelogUpdateOutcome updateOutcome = - _updateChangelog(package, nextVersionString); - switch (updateOutcome) { - case _ChangelogUpdateOutcome.addedSection: - print('${indentation}Added a $nextVersionString section.'); - break; - case _ChangelogUpdateOutcome.updatedSection: - print('${indentation}Updated NEXT section.'); - break; - case _ChangelogUpdateOutcome.failed: - return PackageResult.fail(['Could not update CHANGELOG.md.']); - } - - return PackageResult.success(); - } - - _ChangelogUpdateOutcome _updateChangelog( - RepositoryPackage package, String version) { - if (!package.changelogFile.existsSync()) { - printError('${indentation}Missing CHANGELOG.md.'); - return _ChangelogUpdateOutcome.failed; - } - - final String newHeader = '## $version'; - final RegExp listItemPattern = RegExp(r'^(\s*[-*])'); - - final StringBuffer newChangelog = StringBuffer(); - _ChangelogUpdateState state = _ChangelogUpdateState.findingFirstSection; - bool updatedExistingSection = false; - - for (final String line in package.changelogFile.readAsLinesSync()) { - switch (state) { - case _ChangelogUpdateState.findingFirstSection: - final String trimmedLine = line.trim(); - if (trimmedLine.isEmpty) { - // Discard any whitespace at the top of the file. - } else if (trimmedLine == '## NEXT') { - // Replace the header with the new version (which may also be NEXT). - newChangelog.writeln(newHeader); - // Find the existing list to add to. - state = _ChangelogUpdateState.findingFirstListItem; - } else { - // The first content in the file isn't a NEXT section, so just add - // the new section. - [ - newHeader, - '', - ..._changelogAdditionsAsList(), - '', - line, // Don't drop the current line. - ].forEach(newChangelog.writeln); - state = _ChangelogUpdateState.finishedUpdating; - } - break; - case _ChangelogUpdateState.findingFirstListItem: - final RegExpMatch? match = listItemPattern.firstMatch(line); - if (match != null) { - final String listMarker = match[1]!; - // Add the new items on top. If the new change is changing the - // version, then the new item should be more relevant to package - // clients than anything that was already there. If it's still - // NEXT, the order doesn't matter. - [ - ..._changelogAdditionsAsList(listMarker: listMarker), - line, // Don't drop the current line. - ].forEach(newChangelog.writeln); - state = _ChangelogUpdateState.finishedUpdating; - updatedExistingSection = true; - } else if (line.trim().isEmpty) { - // Scan past empty lines, but keep them. - newChangelog.writeln(line); - } else { - printError(' Existing NEXT section has unrecognized format.'); - return _ChangelogUpdateOutcome.failed; - } - break; - case _ChangelogUpdateState.finishedUpdating: - // Once changes are done, add the rest of the lines as-is. - newChangelog.writeln(line); - break; - } - } - - package.changelogFile.writeAsStringSync(newChangelog.toString()); - - return updatedExistingSection - ? _ChangelogUpdateOutcome.updatedSection - : _ChangelogUpdateOutcome.addedSection; - } - - /// Returns the changelog to add as a Markdown list, using the given list - /// bullet style (default to the repository standard of '*'), and adding - /// any missing periods. - /// - /// E.g., 'A line\nAnother line.' will become: - /// ``` - /// [ '* A line.', '* Another line.' ] - /// ``` - Iterable _changelogAdditionsAsList({String listMarker = '*'}) { - return getStringArg(_changelogFlag).split('\n').map((String entry) { - String standardizedEntry = entry.trim(); - if (!standardizedEntry.endsWith('.')) { - standardizedEntry = '$standardizedEntry.'; - } - return '$listMarker $standardizedEntry'; - }); - } - - /// Updates the version in [package]'s pubspec according to [type], returning - /// the new version, or null if there was an error updating the version. - Version? _updatePubspecVersion( - RepositoryPackage package, _VersionIncrementType type) { - final Pubspec pubspec = package.parsePubspec(); - final Version? currentVersion = pubspec.version; - if (currentVersion == null) { - printError('${indentation}No version in pubspec.yaml'); - return null; - } - - // For versions less than 1.0, shift the change down one component per - // Dart versioning conventions. - final _VersionIncrementType adjustedType = currentVersion.major > 0 - ? type - : _VersionIncrementType.values[type.index - 1]; - - final Version newVersion = _nextVersion(currentVersion, adjustedType); - - // Write the new version to the pubspec. - final YamlEditor editablePubspec = - YamlEditor(package.pubspecFile.readAsStringSync()); - editablePubspec.update(['version'], newVersion.toString()); - package.pubspecFile.writeAsStringSync(editablePubspec.toString()); - - return newVersion; - } - - Version _nextVersion(Version version, _VersionIncrementType type) { - switch (type) { - case _VersionIncrementType.minor: - return version.nextMinor; - case _VersionIncrementType.bugfix: - return version.nextPatch; - case _VersionIncrementType.build: - final int buildNumber = - version.build.isEmpty ? 0 : version.build.first as int; - return Version(version.major, version.minor, version.patch, - build: '${buildNumber + 1}'); - } - } -} diff --git a/script/tool/lib/src/version_check_command.dart b/script/tool/lib/src/version_check_command.dart deleted file mode 100644 index bb53620f06d3..000000000000 --- a/script/tool/lib/src/version_check_command.dart +++ /dev/null @@ -1,591 +0,0 @@ -// Copyright 2013 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. - -import 'package:file/file.dart'; -import 'package:git/git.dart'; -import 'package:http/http.dart' as http; -import 'package:meta/meta.dart'; -import 'package:path/path.dart' as p; -import 'package:platform/platform.dart'; -import 'package:pub_semver/pub_semver.dart'; - -import 'common/core.dart'; -import 'common/git_version_finder.dart'; -import 'common/package_looping_command.dart'; -import 'common/package_state_utils.dart'; -import 'common/process_runner.dart'; -import 'common/pub_version_finder.dart'; -import 'common/repository_package.dart'; - -/// Categories of version change types. -enum NextVersionType { - /// A breaking change. - BREAKING_MAJOR, - - /// A minor change (e.g., added feature). - MINOR, - - /// A bugfix change. - PATCH, - - /// The release of an existing pre-1.0 version. - V1_RELEASE, -} - -/// The state of a package's version relative to the comparison base. -enum _CurrentVersionState { - /// The version is unchanged. - unchanged, - - /// The version has increased, and the transition is valid. - validIncrease, - - /// The version has decrease, and the transition is a valid revert. - validRevert, - - /// The version has changed, and the transition is invalid. - invalidChange, - - /// There was an error determining the version state. - unknown, -} - -/// Returns the set of allowed next non-prerelease versions, with their change -/// type, for [version]. -/// -/// [newVersion] is used to check whether this is a pre-1.0 version bump, as -/// those have different semver rules. -@visibleForTesting -Map getAllowedNextVersions( - Version version, { - required Version newVersion, -}) { - final Map allowedNextVersions = - { - version.nextMajor: NextVersionType.BREAKING_MAJOR, - version.nextMinor: NextVersionType.MINOR, - version.nextPatch: NextVersionType.PATCH, - }; - - if (version.major < 1 && newVersion.major < 1) { - int nextBuildNumber = -1; - if (version.build.isEmpty) { - nextBuildNumber = 1; - } else { - final int currentBuildNumber = version.build.first as int; - nextBuildNumber = currentBuildNumber + 1; - } - final Version nextBuildVersion = Version( - version.major, - version.minor, - version.patch, - build: nextBuildNumber.toString(), - ); - allowedNextVersions.clear(); - allowedNextVersions[version.nextMajor] = NextVersionType.V1_RELEASE; - allowedNextVersions[version.nextMinor] = NextVersionType.BREAKING_MAJOR; - allowedNextVersions[version.nextPatch] = NextVersionType.MINOR; - allowedNextVersions[nextBuildVersion] = NextVersionType.PATCH; - } - return allowedNextVersions; -} - -/// A command to validate version changes to packages. -class VersionCheckCommand extends PackageLoopingCommand { - /// Creates an instance of the version check command. - VersionCheckCommand( - Directory packagesDir, { - ProcessRunner processRunner = const ProcessRunner(), - Platform platform = const LocalPlatform(), - GitDir? gitDir, - http.Client? httpClient, - }) : _pubVersionFinder = - PubVersionFinder(httpClient: httpClient ?? http.Client()), - super( - packagesDir, - processRunner: processRunner, - platform: platform, - gitDir: gitDir, - ) { - argParser.addFlag( - _againstPubFlag, - help: 'Whether the version check should run against the version on pub.\n' - 'Defaults to false, which means the version check only run against ' - 'the previous version in code.', - ); - argParser.addOption(_prLabelsArg, - help: 'A comma-separated list of labels associated with this PR, ' - 'if applicable.\n\n' - 'If supplied, this may be to allow overrides to some version ' - 'checks.'); - argParser.addFlag(_checkForMissingChanges, - help: 'Validates that changes to packages include CHANGELOG and ' - 'version changes unless they meet an established exemption.\n\n' - 'If used with --$_prLabelsArg, this is should only be ' - 'used in pre-submit CI checks, to prevent post-submit breakage ' - 'when labels are no longer applicable.', - hide: true); - argParser.addFlag(_ignorePlatformInterfaceBreaks, - help: 'Bypasses the check that platform interfaces do not contain ' - 'breaking changes.\n\n' - 'This is only intended for use in post-submit CI checks, to ' - 'prevent post-submit breakage when overriding the check with ' - 'labels. Pre-submit checks should always use ' - '--$_prLabelsArg instead.', - hide: true); - } - - static const String _againstPubFlag = 'against-pub'; - static const String _prLabelsArg = 'pr-labels'; - static const String _checkForMissingChanges = 'check-for-missing-changes'; - static const String _ignorePlatformInterfaceBreaks = - 'ignore-platform-interface-breaks'; - - /// The label that must be on a PR to allow a breaking - /// change to a platform interface. - static const String _breakingChangeOverrideLabel = - 'override: allow breaking change'; - - /// The label that must be on a PR to allow skipping a version change for a PR - /// that would normally require one. - static const String _missingVersionChangeOverrideLabel = - 'override: no versioning needed'; - - /// The label that must be on a PR to allow skipping a CHANGELOG change for a - /// PR that would normally require one. - static const String _missingChangelogChangeOverrideLabel = - 'override: no changelog needed'; - - final PubVersionFinder _pubVersionFinder; - - late final GitVersionFinder _gitVersionFinder; - late final String _mergeBase; - late final List _changedFiles; - - late final Set _prLabels = _getPRLabels(); - - @override - final String name = 'version-check'; - - @override - final String description = - 'Checks if the versions of packages have been incremented per pub specification.\n' - 'Also checks if the latest version in CHANGELOG matches the version in pubspec.\n\n' - 'This command requires "pub" and "flutter" to be in your path.'; - - @override - bool get hasLongOutput => false; - - @override - Future initializeRun() async { - _gitVersionFinder = await retrieveVersionFinder(); - _mergeBase = await _gitVersionFinder.getBaseSha(); - _changedFiles = await _gitVersionFinder.getChangedFiles(); - } - - @override - Future runForPackage(RepositoryPackage package) async { - final Pubspec? pubspec = _tryParsePubspec(package); - if (pubspec == null) { - // No remaining checks make sense, so fail immediately. - return PackageResult.fail(['Invalid pubspec.yaml.']); - } - - if (pubspec.publishTo == 'none') { - return PackageResult.skip('Found "publish_to: none".'); - } - - final Version? currentPubspecVersion = pubspec.version; - if (currentPubspecVersion == null) { - printError('${indentation}No version found in pubspec.yaml. A package ' - 'that intentionally has no version should be marked ' - '"publish_to: none".'); - // No remaining checks make sense, so fail immediately. - return PackageResult.fail(['No pubspec.yaml version.']); - } - - final List errors = []; - - bool versionChanged; - final _CurrentVersionState versionState = - await _getVersionState(package, pubspec: pubspec); - switch (versionState) { - case _CurrentVersionState.unchanged: - versionChanged = false; - break; - case _CurrentVersionState.validIncrease: - case _CurrentVersionState.validRevert: - versionChanged = true; - break; - case _CurrentVersionState.invalidChange: - versionChanged = true; - errors.add('Disallowed version change.'); - break; - case _CurrentVersionState.unknown: - versionChanged = false; - errors.add('Unable to determine previous version.'); - break; - } - - if (!(await _validateChangelogVersion(package, - pubspec: pubspec, pubspecVersionState: versionState))) { - errors.add('CHANGELOG.md failed validation.'); - } - - // If there are no other issues, make sure that there isn't a missing - // change to the version and/or CHANGELOG. - if (getBoolArg(_checkForMissingChanges) && - !versionChanged && - errors.isEmpty) { - final String? error = await _checkForMissingChangeError(package); - if (error != null) { - errors.add(error); - } - } - - return errors.isEmpty - ? PackageResult.success() - : PackageResult.fail(errors); - } - - @override - Future completeRun() async { - _pubVersionFinder.httpClient.close(); - } - - /// Returns the previous published version of [package]. - /// - /// [packageName] must be the actual name of the package as published (i.e., - /// the name from pubspec.yaml, not the on disk name if different.) - Future _fetchPreviousVersionFromPub(String packageName) async { - final PubVersionFinderResponse pubVersionFinderResponse = - await _pubVersionFinder.getPackageVersion(packageName: packageName); - switch (pubVersionFinderResponse.result) { - case PubVersionFinderResult.success: - return pubVersionFinderResponse.versions.first; - case PubVersionFinderResult.fail: - printError(''' -${indentation}Error fetching version on pub for $packageName. -${indentation}HTTP Status ${pubVersionFinderResponse.httpResponse.statusCode} -${indentation}HTTP response: ${pubVersionFinderResponse.httpResponse.body} -'''); - return null; - case PubVersionFinderResult.noPackageFound: - return Version.none; - } - } - - /// Returns the version of [package] from git at the base comparison hash. - Future _getPreviousVersionFromGit(RepositoryPackage package) async { - final File pubspecFile = package.pubspecFile; - final String relativePath = - path.relative(pubspecFile.absolute.path, from: (await gitDir).path); - // Use Posix-style paths for git. - final String gitPath = path.style == p.Style.windows - ? p.posix.joinAll(path.split(relativePath)) - : relativePath; - return _gitVersionFinder.getPackageVersion(gitPath, gitRef: _mergeBase); - } - - /// Returns the state of the verison of [package] relative to the comparison - /// base (git or pub, depending on flags). - Future<_CurrentVersionState> _getVersionState( - RepositoryPackage package, { - required Pubspec pubspec, - }) async { - // This method isn't called unless `version` is non-null. - final Version currentVersion = pubspec.version!; - Version? previousVersion; - String previousVersionSource; - if (getBoolArg(_againstPubFlag)) { - previousVersionSource = 'pub'; - previousVersion = await _fetchPreviousVersionFromPub(pubspec.name); - if (previousVersion == null) { - return _CurrentVersionState.unknown; - } - if (previousVersion != Version.none) { - print( - '$indentation${pubspec.name}: Current largest version on pub: $previousVersion'); - } - } else { - previousVersionSource = _mergeBase; - previousVersion = - await _getPreviousVersionFromGit(package) ?? Version.none; - } - if (previousVersion == Version.none) { - print('${indentation}Unable to find previous version ' - '${getBoolArg(_againstPubFlag) ? 'on pub server' : 'at git base'}.'); - logWarning( - '${indentation}If this package is not new, something has gone wrong.'); - return _CurrentVersionState.validIncrease; // Assume new, thus valid. - } - - if (previousVersion == currentVersion) { - print('${indentation}No version change.'); - return _CurrentVersionState.unchanged; - } - - // Check for reverts when doing local validation. - if (!getBoolArg(_againstPubFlag) && currentVersion < previousVersion) { - // Since this skips validation, try to ensure that it really is likely - // to be a revert rather than a typo by checking that the transition - // from the lower version to the new version would have been valid. - if (_shouldAllowVersionChange( - oldVersion: currentVersion, newVersion: previousVersion)) { - logWarning('${indentation}New version is lower than previous version. ' - 'This is assumed to be a revert.'); - return _CurrentVersionState.validRevert; - } - } - - final Map allowedNextVersions = - getAllowedNextVersions(previousVersion, newVersion: currentVersion); - - if (_shouldAllowVersionChange( - oldVersion: previousVersion, newVersion: currentVersion)) { - print('$indentation$previousVersion -> $currentVersion'); - } else { - printError('${indentation}Incorrectly updated version.\n' - '${indentation}HEAD: $currentVersion, $previousVersionSource: $previousVersion.\n' - '${indentation}Allowed versions: $allowedNextVersions'); - return _CurrentVersionState.invalidChange; - } - - // Check whether the version (or for a pre-release, the version that - // pre-release would eventually be released as) is a breaking change, and - // if so, validate it. - final Version targetReleaseVersion = - currentVersion.isPreRelease ? currentVersion.nextPatch : currentVersion; - if (allowedNextVersions[targetReleaseVersion] == - NextVersionType.BREAKING_MAJOR && - !_validateBreakingChange(package)) { - printError('${indentation}Breaking change detected.\n' - '${indentation}Breaking changes to platform interfaces are not ' - 'allowed without explicit justification.\n' - '${indentation}See ' - 'https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages ' - 'for more information.'); - return _CurrentVersionState.invalidChange; - } - - return _CurrentVersionState.validIncrease; - } - - /// Checks whether or not [package]'s CHANGELOG's versioning is correct, - /// both that it matches [pubspec] and that NEXT is used correctly, printing - /// the results of its checks. - /// - /// Returns false if the CHANGELOG fails validation. - Future _validateChangelogVersion( - RepositoryPackage package, { - required Pubspec pubspec, - required _CurrentVersionState pubspecVersionState, - }) async { - // This method isn't called unless `version` is non-null. - final Version fromPubspec = pubspec.version!; - - // get first version from CHANGELOG - final File changelog = package.changelogFile; - final List lines = changelog.readAsLinesSync(); - String? firstLineWithText; - final Iterator iterator = lines.iterator; - while (iterator.moveNext()) { - if (iterator.current.trim().isNotEmpty) { - firstLineWithText = iterator.current.trim(); - break; - } - } - // Remove all leading mark down syntax from the version line. - String? versionString = firstLineWithText?.split(' ').last; - - final String badNextErrorMessage = '${indentation}When bumping the version ' - 'for release, the NEXT section should be incorporated into the new ' - "version's release notes."; - - // Skip validation for the special NEXT version that's used to accumulate - // changes that don't warrant publishing on their own. - final bool hasNextSection = versionString == 'NEXT'; - if (hasNextSection) { - // NEXT should not be present in a commit that increases the version. - if (pubspecVersionState == _CurrentVersionState.validIncrease || - pubspecVersionState == _CurrentVersionState.invalidChange) { - printError(badNextErrorMessage); - return false; - } - print( - '${indentation}Found NEXT; validating next version in the CHANGELOG.'); - // Ensure that the version in pubspec hasn't changed without updating - // CHANGELOG. That means the next version entry in the CHANGELOG should - // pass the normal validation. - versionString = null; - while (iterator.moveNext()) { - if (iterator.current.trim().startsWith('## ')) { - versionString = iterator.current.trim().split(' ').last; - break; - } - } - } - - if (versionString == null) { - printError('${indentation}Unable to find a version in CHANGELOG.md'); - print('${indentation}The current version should be on a line starting ' - 'with "## ", either on the first non-empty line or after a "## NEXT" ' - 'section.'); - return false; - } - - final Version fromChangeLog; - try { - fromChangeLog = Version.parse(versionString); - } on FormatException { - printError('"$versionString" could not be parsed as a version.'); - return false; - } - - if (fromPubspec != fromChangeLog) { - printError(''' -${indentation}Versions in CHANGELOG.md and pubspec.yaml do not match. -${indentation}The version in pubspec.yaml is $fromPubspec. -${indentation}The first version listed in CHANGELOG.md is $fromChangeLog. -'''); - return false; - } - - // If NEXT wasn't the first section, it should not exist at all. - if (!hasNextSection) { - final RegExp nextRegex = RegExp(r'^#+\s*NEXT\s*$'); - if (lines.any((String line) => nextRegex.hasMatch(line))) { - printError(badNextErrorMessage); - return false; - } - } - - return true; - } - - Pubspec? _tryParsePubspec(RepositoryPackage package) { - try { - final Pubspec pubspec = package.parsePubspec(); - return pubspec; - } on Exception catch (exception) { - printError('${indentation}Failed to parse `pubspec.yaml`: $exception}'); - return null; - } - } - - /// Checks whether the current breaking change to [package] should be allowed, - /// logging extra information for auditing when allowing unusual cases. - bool _validateBreakingChange(RepositoryPackage package) { - // Only platform interfaces have breaking change restrictions. - if (!package.isPlatformInterface) { - return true; - } - - if (getBoolArg(_ignorePlatformInterfaceBreaks)) { - logWarning( - '${indentation}Allowing breaking change to ${package.displayName} ' - 'due to --$_ignorePlatformInterfaceBreaks'); - return true; - } - - if (_prLabels.contains(_breakingChangeOverrideLabel)) { - logWarning( - '${indentation}Allowing breaking change to ${package.displayName} ' - 'due to the "$_breakingChangeOverrideLabel" label.'); - return true; - } - - return false; - } - - /// Returns the labels associated with this PR, if any, or an empty set - /// if that flag is not provided. - Set _getPRLabels() { - final String labels = getStringArg(_prLabelsArg); - if (labels.isEmpty) { - return {}; - } - return labels.split(',').map((String label) => label.trim()).toSet(); - } - - /// Returns true if the given version transition should be allowed. - bool _shouldAllowVersionChange( - {required Version oldVersion, required Version newVersion}) { - // Get the non-pre-release next version mapping. - final Map allowedNextVersions = - getAllowedNextVersions(oldVersion, newVersion: newVersion); - - if (allowedNextVersions.containsKey(newVersion)) { - return true; - } - // Allow a pre-release version of a version that would be a valid - // transition. - if (newVersion.isPreRelease) { - final Version targetReleaseVersion = newVersion.nextPatch; - if (allowedNextVersions.containsKey(targetReleaseVersion)) { - return true; - } - } - return false; - } - - /// Returns an error string if the changes to this package should have - /// resulted in a version change, or shoud have resulted in a CHANGELOG change - /// but didn't. - /// - /// This should only be called if the version did not change. - Future _checkForMissingChangeError(RepositoryPackage package) async { - // Find the relative path to the current package, as it would appear at the - // beginning of a path reported by getChangedFiles() (which always uses - // Posix paths). - final Directory gitRoot = - packagesDir.fileSystem.directory((await gitDir).path); - final String relativePackagePath = - getRelativePosixPath(package.directory, from: gitRoot); - - final PackageChangeState state = await checkPackageChangeState(package, - changedPaths: _changedFiles, - relativePackagePath: relativePackagePath, - git: await retrieveVersionFinder()); - - if (!state.hasChanges) { - return null; - } - - if (state.needsVersionChange) { - if (_prLabels.contains(_missingVersionChangeOverrideLabel)) { - logWarning('Ignoring lack of version change due to the ' - '"$_missingVersionChangeOverrideLabel" label.'); - } else { - printError( - 'No version change found, but the change to this package could ' - 'not be verified to be exempt from version changes according to ' - 'repository policy. If this is a false positive, please comment in ' - 'the PR to explain why the PR is exempt, and add (or ask your ' - 'reviewer to add) the "$_missingVersionChangeOverrideLabel" ' - 'label.'); - return 'Missing version change'; - } - } - - if (!state.hasChangelogChange && state.needsChangelogChange) { - if (_prLabels.contains(_missingChangelogChangeOverrideLabel)) { - logWarning('Ignoring lack of CHANGELOG update due to the ' - '"$_missingChangelogChangeOverrideLabel" label.'); - } else { - printError( - 'No CHANGELOG change found. If this PR needs an exemption from ' - 'the standard policy of listing all changes in the CHANGELOG, ' - 'comment in the PR to explain why the PR is exempt, and add (or ' - 'ask your reviewer to add) the ' - '"$_missingChangelogChangeOverrideLabel" label. Otherwise, ' - 'please add a NEXT entry in the CHANGELOG as described in ' - 'the contributing guide.'); - return 'Missing CHANGELOG change'; - } - } - - return null; - } -} diff --git a/script/tool/lib/src/xcode_analyze_command.dart b/script/tool/lib/src/xcode_analyze_command.dart deleted file mode 100644 index a81bf15477af..000000000000 --- a/script/tool/lib/src/xcode_analyze_command.dart +++ /dev/null @@ -1,133 +0,0 @@ -// Copyright 2013 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. - -import 'package:file/file.dart'; -import 'package:platform/platform.dart'; - -import 'common/core.dart'; -import 'common/package_looping_command.dart'; -import 'common/plugin_utils.dart'; -import 'common/process_runner.dart'; -import 'common/repository_package.dart'; -import 'common/xcode.dart'; - -/// The command to run Xcode's static analyzer on plugins. -class XcodeAnalyzeCommand extends PackageLoopingCommand { - /// Creates an instance of the test command. - XcodeAnalyzeCommand( - Directory packagesDir, { - ProcessRunner processRunner = const ProcessRunner(), - Platform platform = const LocalPlatform(), - }) : _xcode = Xcode(processRunner: processRunner, log: true), - super(packagesDir, processRunner: processRunner, platform: platform) { - argParser.addFlag(platformIOS, help: 'Analyze iOS'); - argParser.addFlag(platformMacOS, help: 'Analyze macOS'); - argParser.addOption(_minIOSVersionArg, - help: 'Sets the minimum iOS deployment version to use when compiling, ' - 'overriding the default minimum version. This can be used to find ' - 'deprecation warnings that will affect the plugin in the future.'); - argParser.addOption(_minMacOSVersionArg, - help: - 'Sets the minimum macOS deployment version to use when compiling, ' - 'overriding the default minimum version. This can be used to find ' - 'deprecation warnings that will affect the plugin in the future.'); - } - - static const String _minIOSVersionArg = 'ios-min-version'; - static const String _minMacOSVersionArg = 'macos-min-version'; - - final Xcode _xcode; - - @override - final String name = 'xcode-analyze'; - - @override - final String description = - 'Runs Xcode analysis on the iOS and/or macOS example apps.'; - - @override - Future initializeRun() async { - if (!(getBoolArg(platformIOS) || getBoolArg(platformMacOS))) { - printError('At least one platform flag must be provided.'); - throw ToolExit(exitInvalidArguments); - } - } - - @override - Future runForPackage(RepositoryPackage package) async { - final bool testIOS = getBoolArg(platformIOS) && - pluginSupportsPlatform(platformIOS, package, - requiredMode: PlatformSupport.inline); - final bool testMacOS = getBoolArg(platformMacOS) && - pluginSupportsPlatform(platformMacOS, package, - requiredMode: PlatformSupport.inline); - - final bool multiplePlatformsRequested = - getBoolArg(platformIOS) && getBoolArg(platformMacOS); - if (!(testIOS || testMacOS)) { - return PackageResult.skip('Not implemented for target platform(s).'); - } - - final String minIOSVersion = getStringArg(_minIOSVersionArg); - final String minMacOSVersion = getStringArg(_minMacOSVersionArg); - - final List failures = []; - if (testIOS && - !await _analyzePlugin(package, 'iOS', extraFlags: [ - '-destination', - 'generic/platform=iOS Simulator', - if (minIOSVersion.isNotEmpty) - 'IPHONEOS_DEPLOYMENT_TARGET=$minIOSVersion', - ])) { - failures.add('iOS'); - } - if (testMacOS && - !await _analyzePlugin(package, 'macOS', extraFlags: [ - if (minMacOSVersion.isNotEmpty) - 'MACOSX_DEPLOYMENT_TARGET=$minMacOSVersion', - ])) { - failures.add('macOS'); - } - - // Only provide the failing platform in the failure details if testing - // multiple platforms, otherwise it's just noise. - return failures.isEmpty - ? PackageResult.success() - : PackageResult.fail( - multiplePlatformsRequested ? failures : []); - } - - /// Analyzes [plugin] for [platform], returning true if it passed analysis. - Future _analyzePlugin( - RepositoryPackage plugin, - String platform, { - List extraFlags = const [], - }) async { - bool passing = true; - for (final RepositoryPackage example in plugin.getExamples()) { - // Running tests and static analyzer. - final String examplePath = getRelativePosixPath(example.directory, - from: plugin.directory.parent); - print('Running $platform tests and analyzer for $examplePath...'); - final int exitCode = await _xcode.runXcodeBuild( - example.directory, - actions: ['analyze'], - workspace: '${platform.toLowerCase()}/Runner.xcworkspace', - scheme: 'Runner', - configuration: 'Debug', - extraFlags: [ - ...extraFlags, - 'GCC_TREAT_WARNINGS_AS_ERRORS=YES', - ], - ); - if (exitCode == 0) { - printSuccess('$examplePath ($platform) passed analysis.'); - } else { - printError('$examplePath ($platform) failed analysis.'); - passing = false; - } - } - return passing; - } -} diff --git a/script/tool/pubspec.yaml b/script/tool/pubspec.yaml index 52d23b8f72a3..60884fdeb8ae 100644 --- a/script/tool/pubspec.yaml +++ b/script/tool/pubspec.yaml @@ -2,6 +2,7 @@ name: flutter_plugin_tools description: Productivity utils for flutter/plugins and flutter/packages repository: https://github.com/flutter/plugins/tree/main/script/tool version: 0.13.4+2 +publish_to: none # See README.md dependencies: args: ^2.1.0 diff --git a/script/tool/test/analyze_command_test.dart b/script/tool/test/analyze_command_test.dart deleted file mode 100644 index e6b910960846..000000000000 --- a/script/tool/test/analyze_command_test.dart +++ /dev/null @@ -1,425 +0,0 @@ -// Copyright 2013 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. - -import 'dart:io' as io; - -import 'package:args/command_runner.dart'; -import 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:flutter_plugin_tools/src/analyze_command.dart'; -import 'package:flutter_plugin_tools/src/common/core.dart'; -import 'package:test/test.dart'; - -import 'mocks.dart'; -import 'util.dart'; - -void main() { - late FileSystem fileSystem; - late MockPlatform mockPlatform; - late Directory packagesDir; - late RecordingProcessRunner processRunner; - late CommandRunner runner; - - setUp(() { - fileSystem = MemoryFileSystem(); - mockPlatform = MockPlatform(); - packagesDir = createPackagesDirectory(fileSystem: fileSystem); - processRunner = RecordingProcessRunner(); - final AnalyzeCommand analyzeCommand = AnalyzeCommand( - packagesDir, - processRunner: processRunner, - platform: mockPlatform, - ); - - runner = CommandRunner('analyze_command', 'Test for analyze_command'); - runner.addCommand(analyzeCommand); - }); - - test('analyzes all packages', () async { - final RepositoryPackage package1 = createFakePackage('a', packagesDir); - final RepositoryPackage plugin2 = createFakePlugin('b', packagesDir); - - await runCapturingPrint(runner, ['analyze']); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall('flutter', const ['pub', 'get'], package1.path), - ProcessCall('dart', const ['analyze', '--fatal-infos'], - package1.path), - ProcessCall('flutter', const ['pub', 'get'], plugin2.path), - ProcessCall( - 'dart', const ['analyze', '--fatal-infos'], plugin2.path), - ])); - }); - - test('skips flutter pub get for examples', () async { - final RepositoryPackage plugin1 = createFakePlugin('a', packagesDir); - - await runCapturingPrint(runner, ['analyze']); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall('flutter', const ['pub', 'get'], plugin1.path), - ProcessCall( - 'dart', const ['analyze', '--fatal-infos'], plugin1.path), - ])); - }); - - test('runs flutter pub get for non-example subpackages', () async { - final RepositoryPackage mainPackage = createFakePackage('a', packagesDir); - final Directory otherPackagesDir = - mainPackage.directory.childDirectory('other_packages'); - final RepositoryPackage subpackage1 = - createFakePackage('subpackage1', otherPackagesDir); - final RepositoryPackage subpackage2 = - createFakePackage('subpackage2', otherPackagesDir); - - await runCapturingPrint(runner, ['analyze']); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - 'flutter', const ['pub', 'get'], mainPackage.path), - ProcessCall( - 'flutter', const ['pub', 'get'], subpackage1.path), - ProcessCall( - 'flutter', const ['pub', 'get'], subpackage2.path), - ProcessCall('dart', const ['analyze', '--fatal-infos'], - mainPackage.path), - ])); - }); - - test('passes lib/ directory with --lib-only', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir); - - await runCapturingPrint(runner, ['analyze', '--lib-only']); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall('flutter', const ['pub', 'get'], package.path), - ProcessCall('dart', const ['analyze', '--fatal-infos', 'lib'], - package.path), - ])); - }); - - test('skips when missing lib/ directory with --lib-only', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir); - package.libDirectory.deleteSync(); - - final List output = - await runCapturingPrint(runner, ['analyze', '--lib-only']); - - expect(processRunner.recordedCalls, isEmpty); - expect( - output, - containsAllInOrder([ - contains('SKIPPING: No lib/ directory'), - ]), - ); - }); - - test( - 'does not run flutter pub get for non-example subpackages with --lib-only', - () async { - final RepositoryPackage mainPackage = createFakePackage('a', packagesDir); - final Directory otherPackagesDir = - mainPackage.directory.childDirectory('other_packages'); - createFakePackage('subpackage1', otherPackagesDir); - createFakePackage('subpackage2', otherPackagesDir); - - await runCapturingPrint(runner, ['analyze', '--lib-only']); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - 'flutter', const ['pub', 'get'], mainPackage.path), - ProcessCall('dart', const ['analyze', '--fatal-infos', 'lib'], - mainPackage.path), - ])); - }); - - test("don't elide a non-contained example package", () async { - final RepositoryPackage plugin1 = createFakePlugin('a', packagesDir); - final RepositoryPackage plugin2 = createFakePlugin('example', packagesDir); - - await runCapturingPrint(runner, ['analyze']); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall('flutter', const ['pub', 'get'], plugin1.path), - ProcessCall( - 'dart', const ['analyze', '--fatal-infos'], plugin1.path), - ProcessCall('flutter', const ['pub', 'get'], plugin2.path), - ProcessCall( - 'dart', const ['analyze', '--fatal-infos'], plugin2.path), - ])); - }); - - test('uses a separate analysis sdk', () async { - final RepositoryPackage plugin = createFakePlugin('a', packagesDir); - - await runCapturingPrint( - runner, ['analyze', '--analysis-sdk', 'foo/bar/baz']); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - 'flutter', - const ['pub', 'get'], - plugin.path, - ), - ProcessCall( - 'foo/bar/baz/bin/dart', - const ['analyze', '--fatal-infos'], - plugin.path, - ), - ]), - ); - }); - - test('downgrades first when requested', () async { - final RepositoryPackage plugin = createFakePlugin('a', packagesDir); - - await runCapturingPrint(runner, ['analyze', '--downgrade']); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - 'flutter', - const ['pub', 'downgrade'], - plugin.path, - ), - ProcessCall( - 'flutter', - const ['pub', 'get'], - plugin.path, - ), - ProcessCall( - 'dart', - const ['analyze', '--fatal-infos'], - plugin.path, - ), - ]), - ); - }); - - group('verifies analysis settings', () { - test('fails analysis_options.yaml', () async { - createFakePlugin('foo', packagesDir, - extraFiles: ['analysis_options.yaml']); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['analyze'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains( - 'Found an extra analysis_options.yaml at /packages/foo/analysis_options.yaml'), - contains(' foo:\n' - ' Unexpected local analysis options'), - ]), - ); - }); - - test('fails .analysis_options', () async { - createFakePlugin('foo', packagesDir, - extraFiles: ['.analysis_options']); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['analyze'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains( - 'Found an extra analysis_options.yaml at /packages/foo/.analysis_options'), - contains(' foo:\n' - ' Unexpected local analysis options'), - ]), - ); - }); - - test('takes an allow list', () async { - final RepositoryPackage plugin = createFakePlugin('foo', packagesDir, - extraFiles: ['analysis_options.yaml']); - - await runCapturingPrint( - runner, ['analyze', '--custom-analysis', 'foo']); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall('flutter', const ['pub', 'get'], plugin.path), - ProcessCall('dart', const ['analyze', '--fatal-infos'], - plugin.path), - ])); - }); - - test('takes an allow config file', () async { - final RepositoryPackage plugin = createFakePlugin('foo', packagesDir, - extraFiles: ['analysis_options.yaml']); - final File allowFile = packagesDir.childFile('custom.yaml'); - allowFile.writeAsStringSync('- foo'); - - await runCapturingPrint( - runner, ['analyze', '--custom-analysis', allowFile.path]); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall('flutter', const ['pub', 'get'], plugin.path), - ProcessCall('dart', const ['analyze', '--fatal-infos'], - plugin.path), - ])); - }); - - test('allows an empty config file', () async { - createFakePlugin('foo', packagesDir, - extraFiles: ['analysis_options.yaml']); - final File allowFile = packagesDir.childFile('custom.yaml'); - allowFile.createSync(); - - await expectLater( - () => runCapturingPrint( - runner, ['analyze', '--custom-analysis', allowFile.path]), - throwsA(isA())); - }); - - // See: https://github.com/flutter/flutter/issues/78994 - test('takes an empty allow list', () async { - createFakePlugin('foo', packagesDir, - extraFiles: ['analysis_options.yaml']); - - await expectLater( - () => runCapturingPrint( - runner, ['analyze', '--custom-analysis', '']), - throwsA(isA())); - }); - }); - - test('fails if "pub get" fails', () async { - createFakePlugin('foo', packagesDir); - - processRunner.mockProcessesForExecutable['flutter'] = [ - MockProcess(exitCode: 1) // flutter pub get - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['analyze'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Unable to get dependencies'), - ]), - ); - }); - - test('fails if "pub downgrade" fails', () async { - createFakePlugin('foo', packagesDir); - - processRunner.mockProcessesForExecutable['flutter'] = [ - MockProcess(exitCode: 1) // flutter pub downgrade - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['analyze', '--downgrade'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Unable to downgrade dependencies'), - ]), - ); - }); - - test('fails if "analyze" fails', () async { - createFakePlugin('foo', packagesDir); - - processRunner.mockProcessesForExecutable['dart'] = [ - MockProcess(exitCode: 1) // dart analyze - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['analyze'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('The following packages had errors:'), - contains(' foo'), - ]), - ); - }); - - // Ensure that the command used to analyze flutter/plugins in the Dart repo: - // https://github.com/dart-lang/sdk/blob/main/tools/bots/flutter/analyze_flutter_plugins.sh - // continues to work. - // - // DO NOT remove or modify this test without a coordination plan in place to - // modify the script above, as it is run from source, but out-of-repo. - // Contact stuartmorgan or devoncarew for assistance. - test('Dart repo analyze command works', () async { - final RepositoryPackage plugin = createFakePlugin('foo', packagesDir, - extraFiles: ['analysis_options.yaml']); - final File allowFile = packagesDir.childFile('custom.yaml'); - allowFile.writeAsStringSync('- foo'); - - await runCapturingPrint(runner, [ - // DO NOT change this call; see comment above. - 'analyze', - '--analysis-sdk', - 'foo/bar/baz', - '--custom-analysis', - allowFile.path - ]); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - 'flutter', - const ['pub', 'get'], - plugin.path, - ), - ProcessCall( - 'foo/bar/baz/bin/dart', - const ['analyze', '--fatal-infos'], - plugin.path, - ), - ]), - ); - }); -} diff --git a/script/tool/test/build_examples_command_test.dart b/script/tool/test/build_examples_command_test.dart deleted file mode 100644 index a819e7a12674..000000000000 --- a/script/tool/test/build_examples_command_test.dart +++ /dev/null @@ -1,634 +0,0 @@ -// Copyright 2013 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. - -import 'dart:io' as io; - -import 'package:args/command_runner.dart'; -import 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:flutter_plugin_tools/src/build_examples_command.dart'; -import 'package:flutter_plugin_tools/src/common/core.dart'; -import 'package:flutter_plugin_tools/src/common/plugin_utils.dart'; -import 'package:test/test.dart'; - -import 'mocks.dart'; -import 'util.dart'; - -void main() { - group('build-example', () { - late FileSystem fileSystem; - late MockPlatform mockPlatform; - late Directory packagesDir; - late CommandRunner runner; - late RecordingProcessRunner processRunner; - - setUp(() { - fileSystem = MemoryFileSystem(); - mockPlatform = MockPlatform(); - packagesDir = createPackagesDirectory(fileSystem: fileSystem); - processRunner = RecordingProcessRunner(); - final BuildExamplesCommand command = BuildExamplesCommand( - packagesDir, - processRunner: processRunner, - platform: mockPlatform, - ); - - runner = CommandRunner( - 'build_examples_command', 'Test for build_example_command'); - runner.addCommand(command); - }); - - test('fails if no plaform flags are passed', () async { - Error? commandError; - final List output = await runCapturingPrint( - runner, ['build-examples'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('At least one platform must be provided'), - ])); - }); - - test('fails if building fails', () async { - createFakePlugin('plugin', packagesDir, - platformSupport: { - platformIOS: const PlatformDetails(PlatformSupport.inline), - }); - - processRunner - .mockProcessesForExecutable[getFlutterCommand(mockPlatform)] = - [ - MockProcess(exitCode: 1) // flutter pub get - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['build-examples', '--ios'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('The following packages had errors:'), - contains(' plugin:\n' - ' plugin/example (iOS)'), - ])); - }); - - test('fails if a plugin has no examples', () async { - createFakePlugin('plugin', packagesDir, - examples: [], - platformSupport: { - platformIOS: const PlatformDetails(PlatformSupport.inline) - }); - - processRunner - .mockProcessesForExecutable[getFlutterCommand(mockPlatform)] = - [ - MockProcess(exitCode: 1) // flutter pub get - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['build-examples', '--ios'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('The following packages had errors:'), - contains(' plugin:\n' - ' No examples found'), - ])); - }); - - test('building for iOS when plugin is not set up for iOS results in no-op', - () async { - mockPlatform.isMacOS = true; - createFakePlugin('plugin', packagesDir); - - final List output = - await runCapturingPrint(runner, ['build-examples', '--ios']); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('iOS is not supported by this plugin'), - ]), - ); - - // Output should be empty since running build-examples --macos with no macos - // implementation is a no-op. - expect(processRunner.recordedCalls, orderedEquals([])); - }); - - test('building for iOS', () async { - mockPlatform.isMacOS = true; - final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir, - platformSupport: { - platformIOS: const PlatformDetails(PlatformSupport.inline), - }); - - final Directory pluginExampleDirectory = getExampleDir(plugin); - - final List output = await runCapturingPrint(runner, - ['build-examples', '--ios', '--enable-experiment=exp1']); - - expect( - output, - containsAllInOrder([ - '\nBUILDING plugin/example for iOS', - ]), - ); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - getFlutterCommand(mockPlatform), - const [ - 'build', - 'ios', - '--no-codesign', - '--enable-experiment=exp1' - ], - pluginExampleDirectory.path), - ])); - }); - - test( - 'building for Linux when plugin is not set up for Linux results in no-op', - () async { - mockPlatform.isLinux = true; - createFakePlugin('plugin', packagesDir); - - final List output = await runCapturingPrint( - runner, ['build-examples', '--linux']); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('Linux is not supported by this plugin'), - ]), - ); - - // Output should be empty since running build-examples --linux with no - // Linux implementation is a no-op. - expect(processRunner.recordedCalls, orderedEquals([])); - }); - - test('building for Linux', () async { - mockPlatform.isLinux = true; - final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir, - platformSupport: { - platformLinux: const PlatformDetails(PlatformSupport.inline), - }); - - final Directory pluginExampleDirectory = getExampleDir(plugin); - - final List output = await runCapturingPrint( - runner, ['build-examples', '--linux']); - - expect( - output, - containsAllInOrder([ - '\nBUILDING plugin/example for Linux', - ]), - ); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall(getFlutterCommand(mockPlatform), - const ['build', 'linux'], pluginExampleDirectory.path), - ])); - }); - - test('building for macOS with no implementation results in no-op', - () async { - mockPlatform.isMacOS = true; - createFakePlugin('plugin', packagesDir); - - final List output = await runCapturingPrint( - runner, ['build-examples', '--macos']); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('macOS is not supported by this plugin'), - ]), - ); - - // Output should be empty since running build-examples --macos with no macos - // implementation is a no-op. - expect(processRunner.recordedCalls, orderedEquals([])); - }); - - test('building for macOS', () async { - mockPlatform.isMacOS = true; - final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir, - platformSupport: { - platformMacOS: const PlatformDetails(PlatformSupport.inline), - }); - - final Directory pluginExampleDirectory = getExampleDir(plugin); - - final List output = await runCapturingPrint( - runner, ['build-examples', '--macos']); - - expect( - output, - containsAllInOrder([ - '\nBUILDING plugin/example for macOS', - ]), - ); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall(getFlutterCommand(mockPlatform), - const ['build', 'macos'], pluginExampleDirectory.path), - ])); - }); - - test('building for web with no implementation results in no-op', () async { - createFakePlugin('plugin', packagesDir); - - final List output = - await runCapturingPrint(runner, ['build-examples', '--web']); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('web is not supported by this plugin'), - ]), - ); - - // Output should be empty since running build-examples --macos with no macos - // implementation is a no-op. - expect(processRunner.recordedCalls, orderedEquals([])); - }); - - test('building for web', () async { - final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir, - platformSupport: { - platformWeb: const PlatformDetails(PlatformSupport.inline), - }); - - final Directory pluginExampleDirectory = getExampleDir(plugin); - - final List output = - await runCapturingPrint(runner, ['build-examples', '--web']); - - expect( - output, - containsAllInOrder([ - '\nBUILDING plugin/example for web', - ]), - ); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall(getFlutterCommand(mockPlatform), - const ['build', 'web'], pluginExampleDirectory.path), - ])); - }); - - test( - 'building for Windows when plugin is not set up for Windows results in no-op', - () async { - mockPlatform.isWindows = true; - createFakePlugin('plugin', packagesDir); - - final List output = await runCapturingPrint( - runner, ['build-examples', '--windows']); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('Windows is not supported by this plugin'), - ]), - ); - - // Output should be empty since running build-examples --windows with no - // Windows implementation is a no-op. - expect(processRunner.recordedCalls, orderedEquals([])); - }); - - test('building for Windows', () async { - mockPlatform.isWindows = true; - final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir, - platformSupport: { - platformWindows: const PlatformDetails(PlatformSupport.inline), - }); - - final Directory pluginExampleDirectory = getExampleDir(plugin); - - final List output = await runCapturingPrint( - runner, ['build-examples', '--windows']); - - expect( - output, - containsAllInOrder([ - '\nBUILDING plugin/example for Windows', - ]), - ); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - getFlutterCommand(mockPlatform), - const ['build', 'windows'], - pluginExampleDirectory.path), - ])); - }); - - test( - 'building for Android when plugin is not set up for Android results in no-op', - () async { - createFakePlugin('plugin', packagesDir); - - final List output = - await runCapturingPrint(runner, ['build-examples', '--apk']); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('Android is not supported by this plugin'), - ]), - ); - - // Output should be empty since running build-examples --macos with no macos - // implementation is a no-op. - expect(processRunner.recordedCalls, orderedEquals([])); - }); - - test('building for Android', () async { - final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir, - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline), - }); - - final Directory pluginExampleDirectory = getExampleDir(plugin); - - final List output = await runCapturingPrint(runner, [ - 'build-examples', - '--apk', - ]); - - expect( - output, - containsAllInOrder([ - '\nBUILDING plugin/example for Android (apk)', - ]), - ); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall(getFlutterCommand(mockPlatform), - const ['build', 'apk'], pluginExampleDirectory.path), - ])); - }); - - test('enable-experiment flag for Android', () async { - final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir, - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline), - }); - - final Directory pluginExampleDirectory = getExampleDir(plugin); - - await runCapturingPrint(runner, - ['build-examples', '--apk', '--enable-experiment=exp1']); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - getFlutterCommand(mockPlatform), - const ['build', 'apk', '--enable-experiment=exp1'], - pluginExampleDirectory.path), - ])); - }); - - test('enable-experiment flag for ios', () async { - final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir, - platformSupport: { - platformIOS: const PlatformDetails(PlatformSupport.inline), - }); - - final Directory pluginExampleDirectory = getExampleDir(plugin); - - await runCapturingPrint(runner, - ['build-examples', '--ios', '--enable-experiment=exp1']); - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - getFlutterCommand(mockPlatform), - const [ - 'build', - 'ios', - '--no-codesign', - '--enable-experiment=exp1' - ], - pluginExampleDirectory.path), - ])); - }); - - test('logs skipped platforms', () async { - createFakePlugin('plugin', packagesDir, - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline), - }); - - final List output = await runCapturingPrint( - runner, ['build-examples', '--apk', '--ios', '--macos']); - - expect( - output, - containsAllInOrder([ - contains('Skipping unsupported platform(s): iOS, macOS'), - ]), - ); - }); - - group('packages', () { - test('builds when requested platform is supported by example', () async { - final RepositoryPackage package = createFakePackage( - 'package', packagesDir, isFlutter: true, extraFiles: [ - 'example/ios/Runner.xcodeproj/project.pbxproj' - ]); - - final List output = await runCapturingPrint( - runner, ['build-examples', '--ios']); - - expect( - output, - containsAllInOrder([ - contains('Running for package'), - contains('BUILDING package/example for iOS'), - ]), - ); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - getFlutterCommand(mockPlatform), - const [ - 'build', - 'ios', - '--no-codesign', - ], - getExampleDir(package).path), - ])); - }); - - test('skips non-Flutter examples', () async { - createFakePackage('package', packagesDir); - - final List output = await runCapturingPrint( - runner, ['build-examples', '--ios']); - - expect( - output, - containsAllInOrder([ - contains('Running for package'), - contains('No examples found supporting requested platform(s).'), - ]), - ); - - expect(processRunner.recordedCalls, orderedEquals([])); - }); - - test('skips when there is no example', () async { - createFakePackage('package', packagesDir, - isFlutter: true, examples: []); - - final List output = await runCapturingPrint( - runner, ['build-examples', '--ios']); - - expect( - output, - containsAllInOrder([ - contains('Running for package'), - contains('No examples found supporting requested platform(s).'), - ]), - ); - - expect(processRunner.recordedCalls, orderedEquals([])); - }); - - test('skip when example does not support requested platform', () async { - createFakePackage('package', packagesDir, - isFlutter: true, - extraFiles: ['example/linux/CMakeLists.txt']); - - final List output = await runCapturingPrint( - runner, ['build-examples', '--ios']); - - expect( - output, - containsAllInOrder([ - contains('Running for package'), - contains('Skipping iOS for package/example; not supported.'), - contains('No examples found supporting requested platform(s).'), - ]), - ); - - expect(processRunner.recordedCalls, orderedEquals([])); - }); - - test('logs skipped platforms when only some are supported', () async { - final RepositoryPackage package = createFakePackage( - 'package', packagesDir, - isFlutter: true, - extraFiles: ['example/linux/CMakeLists.txt']); - - final List output = await runCapturingPrint( - runner, ['build-examples', '--apk', '--linux']); - - expect( - output, - containsAllInOrder([ - contains('Running for package'), - contains('Building for: Android, Linux'), - contains('Skipping Android for package/example; not supported.'), - ]), - ); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - getFlutterCommand(mockPlatform), - const ['build', 'linux'], - getExampleDir(package).path), - ])); - }); - }); - - test('The .pluginToolsConfig.yaml file', () async { - mockPlatform.isLinux = true; - final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir, - platformSupport: { - platformLinux: const PlatformDetails(PlatformSupport.inline), - platformMacOS: const PlatformDetails(PlatformSupport.inline), - }); - - final Directory pluginExampleDirectory = getExampleDir(plugin); - - final File pluginExampleConfigFile = - pluginExampleDirectory.childFile('.pluginToolsConfig.yaml'); - pluginExampleConfigFile - .writeAsStringSync('buildFlags:\n global:\n - "test argument"'); - - final List output = [ - ...await runCapturingPrint( - runner, ['build-examples', '--linux']), - ...await runCapturingPrint( - runner, ['build-examples', '--macos']), - ]; - - expect( - output, - containsAllInOrder([ - '\nBUILDING plugin/example for Linux', - '\nBUILDING plugin/example for macOS', - ]), - ); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - getFlutterCommand(mockPlatform), - const ['build', 'linux', 'test argument'], - pluginExampleDirectory.path), - ProcessCall( - getFlutterCommand(mockPlatform), - const ['build', 'macos', 'test argument'], - pluginExampleDirectory.path), - ])); - }); - }); -} diff --git a/script/tool/test/common/file_utils_test.dart b/script/tool/test/common/file_utils_test.dart deleted file mode 100644 index 79b804e31ea5..000000000000 --- a/script/tool/test/common/file_utils_test.dart +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2013 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. - -import 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:flutter_plugin_tools/src/common/file_utils.dart'; -import 'package:test/test.dart'; - -void main() { - test('works on Posix', () async { - final FileSystem fileSystem = - MemoryFileSystem(); - - final Directory base = fileSystem.directory('/').childDirectory('base'); - final File file = - childFileWithSubcomponents(base, ['foo', 'bar', 'baz.txt']); - - expect(file.absolute.path, '/base/foo/bar/baz.txt'); - }); - - test('works on Windows', () async { - final FileSystem fileSystem = - MemoryFileSystem(style: FileSystemStyle.windows); - - final Directory base = fileSystem.directory(r'C:\').childDirectory('base'); - final File file = - childFileWithSubcomponents(base, ['foo', 'bar', 'baz.txt']); - - expect(file.absolute.path, r'C:\base\foo\bar\baz.txt'); - }); -} diff --git a/script/tool/test/common/git_version_finder_test.dart b/script/tool/test/common/git_version_finder_test.dart deleted file mode 100644 index 538b72a90021..000000000000 --- a/script/tool/test/common/git_version_finder_test.dart +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright 2013 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. - -import 'dart:io'; - -import 'package:flutter_plugin_tools/src/common/git_version_finder.dart'; -import 'package:mockito/mockito.dart'; -import 'package:test/test.dart'; - -import 'package_command_test.mocks.dart'; - -void main() { - late List?> gitDirCommands; - late String gitDiffResponse; - late MockGitDir gitDir; - String mergeBaseResponse = ''; - - setUp(() { - gitDirCommands = ?>[]; - gitDiffResponse = ''; - gitDir = MockGitDir(); - when(gitDir.runCommand(any, throwOnError: anyNamed('throwOnError'))) - .thenAnswer((Invocation invocation) { - final List arguments = - invocation.positionalArguments[0]! as List; - gitDirCommands.add(arguments); - final MockProcessResult mockProcessResult = MockProcessResult(); - if (arguments[0] == 'diff') { - when(mockProcessResult.stdout as String?) - .thenReturn(gitDiffResponse); - } else if (arguments[0] == 'merge-base') { - when(mockProcessResult.stdout as String?) - .thenReturn(mergeBaseResponse); - } - return Future.value(mockProcessResult); - }); - }); - - test('No git diff should result no files changed', () async { - final GitVersionFinder finder = GitVersionFinder(gitDir, 'some base sha'); - final List changedFiles = await finder.getChangedFiles(); - - expect(changedFiles, isEmpty); - }); - - test('get correct files changed based on git diff', () async { - gitDiffResponse = ''' -file1/file1.cc -file2/file2.cc -'''; - final GitVersionFinder finder = GitVersionFinder(gitDir, 'some base sha'); - final List changedFiles = await finder.getChangedFiles(); - - expect(changedFiles, equals(['file1/file1.cc', 'file2/file2.cc'])); - }); - - test('get correct pubspec change based on git diff', () async { - gitDiffResponse = ''' -file1/pubspec.yaml -file2/file2.cc -'''; - final GitVersionFinder finder = GitVersionFinder(gitDir, 'some base sha'); - final List changedFiles = await finder.getChangedPubSpecs(); - - expect(changedFiles, equals(['file1/pubspec.yaml'])); - }); - - test('use correct base sha if not specified', () async { - mergeBaseResponse = 'shaqwiueroaaidf12312jnadf123nd'; - gitDiffResponse = ''' -file1/pubspec.yaml -file2/file2.cc -'''; - - final GitVersionFinder finder = GitVersionFinder(gitDir, null); - await finder.getChangedFiles(); - verify(gitDir.runCommand( - ['diff', '--name-only', mergeBaseResponse, 'HEAD'])); - }); - - test('use correct base sha if specified', () async { - const String customBaseSha = 'aklsjdcaskf12312'; - gitDiffResponse = ''' -file1/pubspec.yaml -file2/file2.cc -'''; - final GitVersionFinder finder = GitVersionFinder(gitDir, customBaseSha); - await finder.getChangedFiles(); - verify(gitDir - .runCommand(['diff', '--name-only', customBaseSha, 'HEAD'])); - }); - - test('include uncommitted files if requested', () async { - const String customBaseSha = 'aklsjdcaskf12312'; - gitDiffResponse = ''' -file1/pubspec.yaml -file2/file2.cc -'''; - final GitVersionFinder finder = GitVersionFinder(gitDir, customBaseSha); - await finder.getChangedFiles(includeUncommitted: true); - // The call should not have HEAD as a final argument like the default diff. - verify(gitDir.runCommand(['diff', '--name-only', customBaseSha])); - }); -} - -class MockProcessResult extends Mock implements ProcessResult {} diff --git a/script/tool/test/common/gradle_test.dart b/script/tool/test/common/gradle_test.dart deleted file mode 100644 index 8df4a65b93a5..000000000000 --- a/script/tool/test/common/gradle_test.dart +++ /dev/null @@ -1,188 +0,0 @@ -// Copyright 2013 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. - -import 'dart:io' as io; - -import 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:flutter_plugin_tools/src/common/gradle.dart'; -import 'package:test/test.dart'; - -import '../mocks.dart'; -import '../util.dart'; - -void main() { - late FileSystem fileSystem; - late RecordingProcessRunner processRunner; - - setUp(() { - fileSystem = MemoryFileSystem(); - processRunner = RecordingProcessRunner(); - }); - - group('isConfigured', () { - test('reports true when configured on Windows', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', fileSystem.directory('/'), - extraFiles: ['android/gradlew.bat']); - final GradleProject project = GradleProject( - plugin, - processRunner: processRunner, - platform: MockPlatform(isWindows: true), - ); - - expect(project.isConfigured(), true); - }); - - test('reports true when configured on non-Windows', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', fileSystem.directory('/'), - extraFiles: ['android/gradlew']); - final GradleProject project = GradleProject( - plugin, - processRunner: processRunner, - platform: MockPlatform(isMacOS: true), - ); - - expect(project.isConfigured(), true); - }); - - test('reports false when not configured on Windows', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', fileSystem.directory('/'), - extraFiles: ['android/foo']); - final GradleProject project = GradleProject( - plugin, - processRunner: processRunner, - platform: MockPlatform(isWindows: true), - ); - - expect(project.isConfigured(), false); - }); - - test('reports true when configured on non-Windows', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', fileSystem.directory('/'), - extraFiles: ['android/foo']); - final GradleProject project = GradleProject( - plugin, - processRunner: processRunner, - platform: MockPlatform(isMacOS: true), - ); - - expect(project.isConfigured(), false); - }); - }); - - group('runCommand', () { - test('runs without arguments', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', fileSystem.directory('/'), - extraFiles: ['android/gradlew']); - final GradleProject project = GradleProject( - plugin, - processRunner: processRunner, - platform: MockPlatform(isMacOS: true), - ); - - final int exitCode = await project.runCommand('foo'); - - expect(exitCode, 0); - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - plugin - .platformDirectory(FlutterPlatform.android) - .childFile('gradlew') - .path, - const [ - 'foo', - ], - plugin.platformDirectory(FlutterPlatform.android).path), - ])); - }); - - test('runs with arguments', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', fileSystem.directory('/'), - extraFiles: ['android/gradlew']); - final GradleProject project = GradleProject( - plugin, - processRunner: processRunner, - platform: MockPlatform(isMacOS: true), - ); - - final int exitCode = await project.runCommand( - 'foo', - arguments: ['--bar', '--baz'], - ); - - expect(exitCode, 0); - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - plugin - .platformDirectory(FlutterPlatform.android) - .childFile('gradlew') - .path, - const [ - 'foo', - '--bar', - '--baz', - ], - plugin.platformDirectory(FlutterPlatform.android).path), - ])); - }); - - test('runs with the correct wrapper on Windows', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', fileSystem.directory('/'), - extraFiles: ['android/gradlew.bat']); - final GradleProject project = GradleProject( - plugin, - processRunner: processRunner, - platform: MockPlatform(isWindows: true), - ); - - final int exitCode = await project.runCommand('foo'); - - expect(exitCode, 0); - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - plugin - .platformDirectory(FlutterPlatform.android) - .childFile('gradlew.bat') - .path, - const [ - 'foo', - ], - plugin.platformDirectory(FlutterPlatform.android).path), - ])); - }); - - test('returns error codes', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', fileSystem.directory('/'), - extraFiles: ['android/gradlew.bat']); - final GradleProject project = GradleProject( - plugin, - processRunner: processRunner, - platform: MockPlatform(isWindows: true), - ); - - processRunner.mockProcessesForExecutable[project.gradleWrapper.path] = - [ - MockProcess(exitCode: 1), - ]; - - final int exitCode = await project.runCommand('foo'); - - expect(exitCode, 1); - }); - }); -} diff --git a/script/tool/test/common/package_command_test.dart b/script/tool/test/common/package_command_test.dart deleted file mode 100644 index 3620f8fd63a9..000000000000 --- a/script/tool/test/common/package_command_test.dart +++ /dev/null @@ -1,1151 +0,0 @@ -// Copyright 2013 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. - -import 'dart:io'; - -import 'package:args/command_runner.dart'; -import 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:flutter_plugin_tools/src/common/core.dart'; -import 'package:flutter_plugin_tools/src/common/package_command.dart'; -import 'package:flutter_plugin_tools/src/common/process_runner.dart'; -import 'package:git/git.dart'; -import 'package:mockito/annotations.dart'; -import 'package:mockito/mockito.dart'; -import 'package:platform/platform.dart'; -import 'package:test/test.dart'; - -import '../mocks.dart'; -import '../util.dart'; -import 'package_command_test.mocks.dart'; - -@GenerateMocks([GitDir]) -void main() { - late RecordingProcessRunner processRunner; - late SamplePackageCommand command; - late CommandRunner runner; - late FileSystem fileSystem; - late MockPlatform mockPlatform; - late Directory packagesDir; - late Directory thirdPartyPackagesDir; - - setUp(() { - fileSystem = MemoryFileSystem(); - mockPlatform = MockPlatform(); - packagesDir = createPackagesDirectory(fileSystem: fileSystem); - thirdPartyPackagesDir = packagesDir.parent - .childDirectory('third_party') - .childDirectory('packages'); - - final MockGitDir gitDir = MockGitDir(); - when(gitDir.runCommand(any, throwOnError: anyNamed('throwOnError'))) - .thenAnswer((Invocation invocation) { - final List arguments = - invocation.positionalArguments[0]! as List; - // Attach the first argument to the command to make targeting the mock - // results easier. - final String gitCommand = arguments.removeAt(0); - return processRunner.run('git-$gitCommand', arguments); - }); - processRunner = RecordingProcessRunner(); - command = SamplePackageCommand( - packagesDir, - processRunner: processRunner, - platform: mockPlatform, - gitDir: gitDir, - ); - runner = - CommandRunner('common_command', 'Test for common functionality'); - runner.addCommand(command); - }); - - group('plugin iteration', () { - test('all plugins from file system', () async { - final RepositoryPackage plugin1 = - createFakePlugin('plugin1', packagesDir); - final RepositoryPackage plugin2 = - createFakePlugin('plugin2', packagesDir); - await runCapturingPrint(runner, ['sample']); - expect(command.plugins, - unorderedEquals([plugin1.path, plugin2.path])); - }); - - test('includes both plugins and packages', () async { - final RepositoryPackage plugin1 = - createFakePlugin('plugin1', packagesDir); - final RepositoryPackage plugin2 = - createFakePlugin('plugin2', packagesDir); - final RepositoryPackage package3 = - createFakePackage('package3', packagesDir); - final RepositoryPackage package4 = - createFakePackage('package4', packagesDir); - await runCapturingPrint(runner, ['sample']); - expect( - command.plugins, - unorderedEquals([ - plugin1.path, - plugin2.path, - package3.path, - package4.path, - ])); - }); - - test('includes packages without source', () async { - final RepositoryPackage package = - createFakePackage('package', packagesDir); - package.libDirectory.deleteSync(recursive: true); - - await runCapturingPrint(runner, ['sample']); - expect( - command.plugins, - unorderedEquals([ - package.path, - ])); - }); - - test('all plugins includes third_party/packages', () async { - final RepositoryPackage plugin1 = - createFakePlugin('plugin1', packagesDir); - final RepositoryPackage plugin2 = - createFakePlugin('plugin2', packagesDir); - final RepositoryPackage plugin3 = - createFakePlugin('plugin3', thirdPartyPackagesDir); - await runCapturingPrint(runner, ['sample']); - expect(command.plugins, - unorderedEquals([plugin1.path, plugin2.path, plugin3.path])); - }); - - test('--packages limits packages', () async { - final RepositoryPackage plugin1 = - createFakePlugin('plugin1', packagesDir); - createFakePlugin('plugin2', packagesDir); - createFakePackage('package3', packagesDir); - final RepositoryPackage package4 = - createFakePackage('package4', packagesDir); - await runCapturingPrint( - runner, ['sample', '--packages=plugin1,package4']); - expect( - command.plugins, - unorderedEquals([ - plugin1.path, - package4.path, - ])); - }); - - test('--plugins acts as an alias to --packages', () async { - final RepositoryPackage plugin1 = - createFakePlugin('plugin1', packagesDir); - createFakePlugin('plugin2', packagesDir); - createFakePackage('package3', packagesDir); - final RepositoryPackage package4 = - createFakePackage('package4', packagesDir); - await runCapturingPrint( - runner, ['sample', '--plugins=plugin1,package4']); - expect( - command.plugins, - unorderedEquals([ - plugin1.path, - package4.path, - ])); - }); - - test('exclude packages when packages flag is specified', () async { - createFakePlugin('plugin1', packagesDir); - final RepositoryPackage plugin2 = - createFakePlugin('plugin2', packagesDir); - await runCapturingPrint(runner, [ - 'sample', - '--packages=plugin1,plugin2', - '--exclude=plugin1' - ]); - expect(command.plugins, unorderedEquals([plugin2.path])); - }); - - test("exclude packages when packages flag isn't specified", () async { - createFakePlugin('plugin1', packagesDir); - createFakePlugin('plugin2', packagesDir); - await runCapturingPrint( - runner, ['sample', '--exclude=plugin1,plugin2']); - expect(command.plugins, unorderedEquals([])); - }); - - test('exclude federated plugins when packages flag is specified', () async { - createFakePlugin('plugin1', packagesDir.childDirectory('federated')); - final RepositoryPackage plugin2 = - createFakePlugin('plugin2', packagesDir); - await runCapturingPrint(runner, [ - 'sample', - '--packages=federated/plugin1,plugin2', - '--exclude=federated/plugin1' - ]); - expect(command.plugins, unorderedEquals([plugin2.path])); - }); - - test('exclude entire federated plugins when packages flag is specified', - () async { - createFakePlugin('plugin1', packagesDir.childDirectory('federated')); - final RepositoryPackage plugin2 = - createFakePlugin('plugin2', packagesDir); - await runCapturingPrint(runner, [ - 'sample', - '--packages=federated/plugin1,plugin2', - '--exclude=federated' - ]); - expect(command.plugins, unorderedEquals([plugin2.path])); - }); - - test('exclude accepts config files', () async { - createFakePlugin('plugin1', packagesDir); - final File configFile = packagesDir.childFile('exclude.yaml'); - configFile.writeAsStringSync('- plugin1'); - - await runCapturingPrint(runner, [ - 'sample', - '--packages=plugin1', - '--exclude=${configFile.path}' - ]); - expect(command.plugins, unorderedEquals([])); - }); - - test( - 'explicitly specifying the plugin (group) name of a federated plugin ' - 'should include all plugins in the group', () async { - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: ''' -packages/plugin1/plugin1/plugin1.dart -'''), - ]; - final Directory pluginGroup = packagesDir.childDirectory('plugin1'); - final RepositoryPackage appFacingPackage = - createFakePlugin('plugin1', pluginGroup); - final RepositoryPackage platformInterfacePackage = - createFakePlugin('plugin1_platform_interface', pluginGroup); - final RepositoryPackage implementationPackage = - createFakePlugin('plugin1_web', pluginGroup); - - await runCapturingPrint( - runner, ['sample', '--base-sha=main', '--packages=plugin1']); - - expect( - command.plugins, - unorderedEquals([ - appFacingPackage.path, - platformInterfacePackage.path, - implementationPackage.path - ])); - }); - - test( - 'specifying the app-facing package of a federated plugin using its ' - 'fully qualified name should include only that package', () async { - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: ''' -packages/plugin1/plugin1/plugin1.dart -'''), - ]; - final Directory pluginGroup = packagesDir.childDirectory('plugin1'); - final RepositoryPackage appFacingPackage = - createFakePlugin('plugin1', pluginGroup); - createFakePlugin('plugin1_platform_interface', pluginGroup); - createFakePlugin('plugin1_web', pluginGroup); - - await runCapturingPrint(runner, - ['sample', '--base-sha=main', '--packages=plugin1/plugin1']); - - expect(command.plugins, unorderedEquals([appFacingPackage.path])); - }); - - test( - 'specifying a package of a federated plugin by its name should ' - 'include only that package', () async { - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: ''' -packages/plugin1/plugin1/plugin1.dart -'''), - ]; - final Directory pluginGroup = packagesDir.childDirectory('plugin1'); - - createFakePlugin('plugin1', pluginGroup); - final RepositoryPackage platformInterfacePackage = - createFakePlugin('plugin1_platform_interface', pluginGroup); - createFakePlugin('plugin1_web', pluginGroup); - - await runCapturingPrint(runner, [ - 'sample', - '--base-sha=main', - '--packages=plugin1_platform_interface' - ]); - - expect(command.plugins, - unorderedEquals([platformInterfacePackage.path])); - }); - - test('returns subpackages after the enclosing package', () async { - final SamplePackageCommand localCommand = SamplePackageCommand( - packagesDir, - processRunner: processRunner, - platform: mockPlatform, - gitDir: MockGitDir(), - includeSubpackages: true, - ); - final CommandRunner localRunner = - CommandRunner('common_command', 'subpackage testing'); - localRunner.addCommand(localCommand); - - final RepositoryPackage package = - createFakePackage('apackage', packagesDir); - - await runCapturingPrint(localRunner, ['sample']); - expect( - localCommand.plugins, - containsAllInOrder([ - package.path, - getExampleDir(package).path, - ])); - }); - - group('conflicting package selection', () { - test('does not allow --packages with --run-on-changed-packages', - () async { - Error? commandError; - final List output = await runCapturingPrint(runner, [ - 'sample', - '--run-on-changed-packages', - '--packages=plugin1', - ], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Only one of --packages, --run-on-changed-packages, or ' - '--packages-for-branch can be provided.') - ])); - }); - - test('does not allow --packages with --packages-for-branch', () async { - Error? commandError; - final List output = await runCapturingPrint(runner, [ - 'sample', - '--packages-for-branch', - '--packages=plugin1', - ], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Only one of --packages, --run-on-changed-packages, or ' - '--packages-for-branch can be provided.') - ])); - }); - - test( - 'does not allow --run-on-changed-packages with --packages-for-branch', - () async { - Error? commandError; - final List output = await runCapturingPrint(runner, [ - 'sample', - '--packages-for-branch', - '--packages=plugin1', - ], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Only one of --packages, --run-on-changed-packages, or ' - '--packages-for-branch can be provided.') - ])); - }); - }); - - group('test run-on-changed-packages', () { - test('all plugins should be tested if there are no changes.', () async { - final RepositoryPackage plugin1 = - createFakePlugin('plugin1', packagesDir); - final RepositoryPackage plugin2 = - createFakePlugin('plugin2', packagesDir); - await runCapturingPrint(runner, - ['sample', '--base-sha=main', '--run-on-changed-packages']); - - expect(command.plugins, - unorderedEquals([plugin1.path, plugin2.path])); - }); - - test( - 'all plugins should be tested if there are no plugin related changes.', - () async { - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: 'AUTHORS'), - ]; - final RepositoryPackage plugin1 = - createFakePlugin('plugin1', packagesDir); - final RepositoryPackage plugin2 = - createFakePlugin('plugin2', packagesDir); - await runCapturingPrint(runner, - ['sample', '--base-sha=main', '--run-on-changed-packages']); - - expect(command.plugins, - unorderedEquals([plugin1.path, plugin2.path])); - }); - - test('all plugins should be tested if .cirrus.yml changes.', () async { - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: ''' -.cirrus.yml -packages/plugin1/CHANGELOG -'''), - ]; - final RepositoryPackage plugin1 = - createFakePlugin('plugin1', packagesDir); - final RepositoryPackage plugin2 = - createFakePlugin('plugin2', packagesDir); - - final List output = await runCapturingPrint(runner, - ['sample', '--base-sha=main', '--run-on-changed-packages']); - - expect(command.plugins, - unorderedEquals([plugin1.path, plugin2.path])); - expect( - output, - containsAllInOrder([ - contains('Running for all packages, since a file has changed ' - 'that could affect the entire repository.') - ])); - }); - - test('all plugins should be tested if .ci.yaml changes', () async { - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: ''' -.ci.yaml -packages/plugin1/CHANGELOG -'''), - ]; - final RepositoryPackage plugin1 = - createFakePlugin('plugin1', packagesDir); - final RepositoryPackage plugin2 = - createFakePlugin('plugin2', packagesDir); - final List output = await runCapturingPrint(runner, - ['sample', '--base-sha=main', '--run-on-changed-packages']); - - expect(command.plugins, - unorderedEquals([plugin1.path, plugin2.path])); - expect( - output, - containsAllInOrder([ - contains('Running for all packages, since a file has changed ' - 'that could affect the entire repository.') - ])); - }); - - test('all plugins should be tested if anything in .ci/ changes', - () async { - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: ''' -.ci/Dockerfile -packages/plugin1/CHANGELOG -'''), - ]; - final RepositoryPackage plugin1 = - createFakePlugin('plugin1', packagesDir); - final RepositoryPackage plugin2 = - createFakePlugin('plugin2', packagesDir); - final List output = await runCapturingPrint(runner, - ['sample', '--base-sha=main', '--run-on-changed-packages']); - - expect(command.plugins, - unorderedEquals([plugin1.path, plugin2.path])); - expect( - output, - containsAllInOrder([ - contains('Running for all packages, since a file has changed ' - 'that could affect the entire repository.') - ])); - }); - - test('all plugins should be tested if anything in script/ changes.', - () async { - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: ''' -script/tool_runner.sh -packages/plugin1/CHANGELOG -'''), - ]; - final RepositoryPackage plugin1 = - createFakePlugin('plugin1', packagesDir); - final RepositoryPackage plugin2 = - createFakePlugin('plugin2', packagesDir); - final List output = await runCapturingPrint(runner, - ['sample', '--base-sha=main', '--run-on-changed-packages']); - - expect(command.plugins, - unorderedEquals([plugin1.path, plugin2.path])); - expect( - output, - containsAllInOrder([ - contains('Running for all packages, since a file has changed ' - 'that could affect the entire repository.') - ])); - }); - - test('all plugins should be tested if the root analysis options change.', - () async { - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: ''' -analysis_options.yaml -packages/plugin1/CHANGELOG -'''), - ]; - final RepositoryPackage plugin1 = - createFakePlugin('plugin1', packagesDir); - final RepositoryPackage plugin2 = - createFakePlugin('plugin2', packagesDir); - final List output = await runCapturingPrint(runner, - ['sample', '--base-sha=main', '--run-on-changed-packages']); - - expect(command.plugins, - unorderedEquals([plugin1.path, plugin2.path])); - expect( - output, - containsAllInOrder([ - contains('Running for all packages, since a file has changed ' - 'that could affect the entire repository.') - ])); - }); - - test('all plugins should be tested if formatting options change.', - () async { - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: ''' -.clang-format -packages/plugin1/CHANGELOG -'''), - ]; - final RepositoryPackage plugin1 = - createFakePlugin('plugin1', packagesDir); - final RepositoryPackage plugin2 = - createFakePlugin('plugin2', packagesDir); - final List output = await runCapturingPrint(runner, - ['sample', '--base-sha=main', '--run-on-changed-packages']); - - expect(command.plugins, - unorderedEquals([plugin1.path, plugin2.path])); - expect( - output, - containsAllInOrder([ - contains('Running for all packages, since a file has changed ' - 'that could affect the entire repository.') - ])); - }); - - test('Only changed plugin should be tested.', () async { - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: 'packages/plugin1/plugin1.dart'), - ]; - final RepositoryPackage plugin1 = - createFakePlugin('plugin1', packagesDir); - createFakePlugin('plugin2', packagesDir); - final List output = await runCapturingPrint(runner, - ['sample', '--base-sha=main', '--run-on-changed-packages']); - - expect( - output, - containsAllInOrder([ - contains( - 'Running for all packages that have diffs relative to "main"'), - ])); - - expect(command.plugins, unorderedEquals([plugin1.path])); - }); - - test('multiple files in one plugin should also test the plugin', - () async { - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: ''' -packages/plugin1/plugin1.dart -packages/plugin1/ios/plugin1.m -'''), - ]; - final RepositoryPackage plugin1 = - createFakePlugin('plugin1', packagesDir); - createFakePlugin('plugin2', packagesDir); - await runCapturingPrint(runner, - ['sample', '--base-sha=main', '--run-on-changed-packages']); - - expect(command.plugins, unorderedEquals([plugin1.path])); - }); - - test('multiple plugins changed should test all the changed plugins', - () async { - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: ''' -packages/plugin1/plugin1.dart -packages/plugin2/ios/plugin2.m -'''), - ]; - final RepositoryPackage plugin1 = - createFakePlugin('plugin1', packagesDir); - final RepositoryPackage plugin2 = - createFakePlugin('plugin2', packagesDir); - createFakePlugin('plugin3', packagesDir); - await runCapturingPrint(runner, - ['sample', '--base-sha=main', '--run-on-changed-packages']); - - expect(command.plugins, - unorderedEquals([plugin1.path, plugin2.path])); - }); - - test( - 'multiple plugins inside the same plugin group changed should output the plugin group name', - () async { - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: ''' -packages/plugin1/plugin1/plugin1.dart -packages/plugin1/plugin1_platform_interface/plugin1_platform_interface.dart -packages/plugin1/plugin1_web/plugin1_web.dart -'''), - ]; - final RepositoryPackage plugin1 = - createFakePlugin('plugin1', packagesDir.childDirectory('plugin1')); - createFakePlugin('plugin2', packagesDir); - createFakePlugin('plugin3', packagesDir); - await runCapturingPrint(runner, - ['sample', '--base-sha=main', '--run-on-changed-packages']); - - expect(command.plugins, unorderedEquals([plugin1.path])); - }); - - test( - 'changing one plugin in a federated group should only include that plugin', - () async { - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: ''' -packages/plugin1/plugin1/plugin1.dart -'''), - ]; - final RepositoryPackage plugin1 = - createFakePlugin('plugin1', packagesDir.childDirectory('plugin1')); - createFakePlugin('plugin1_platform_interface', - packagesDir.childDirectory('plugin1')); - createFakePlugin('plugin1_web', packagesDir.childDirectory('plugin1')); - await runCapturingPrint(runner, - ['sample', '--base-sha=main', '--run-on-changed-packages']); - - expect(command.plugins, unorderedEquals([plugin1.path])); - }); - - test('--exclude flag works with --run-on-changed-packages', () async { - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: ''' -packages/plugin1/plugin1.dart -packages/plugin2/ios/plugin2.m -packages/plugin3/plugin3.dart -'''), - ]; - final RepositoryPackage plugin1 = - createFakePlugin('plugin1', packagesDir.childDirectory('plugin1')); - createFakePlugin('plugin2', packagesDir); - createFakePlugin('plugin3', packagesDir); - await runCapturingPrint(runner, [ - 'sample', - '--exclude=plugin2,plugin3', - '--base-sha=main', - '--run-on-changed-packages' - ]); - - expect(command.plugins, unorderedEquals([plugin1.path])); - }); - }); - - group('test run-on-dirty-packages', () { - test('no packages should be tested if there are no changes.', () async { - createFakePackage('a_package', packagesDir); - await runCapturingPrint( - runner, ['sample', '--run-on-dirty-packages']); - - expect(command.plugins, unorderedEquals([])); - }); - - test( - 'no packages should be tested if there are no plugin related changes.', - () async { - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: 'AUTHORS'), - ]; - createFakePackage('a_package', packagesDir); - await runCapturingPrint( - runner, ['sample', '--run-on-dirty-packages']); - - expect(command.plugins, unorderedEquals([])); - }); - - test('no packages should be tested even if special repo files change.', - () async { - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: ''' -.cirrus.yml -.ci.yaml -.ci/Dockerfile -.clang-format -analysis_options.yaml -script/tool_runner.sh -'''), - ]; - createFakePackage('a_package', packagesDir); - await runCapturingPrint( - runner, ['sample', '--run-on-dirty-packages']); - - expect(command.plugins, unorderedEquals([])); - }); - - test('Only changed packages should be tested.', () async { - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: 'packages/a_package/lib/a_package.dart'), - ]; - final RepositoryPackage packageA = - createFakePackage('a_package', packagesDir); - createFakePlugin('b_package', packagesDir); - final List output = await runCapturingPrint( - runner, ['sample', '--run-on-dirty-packages']); - - expect( - output, - containsAllInOrder([ - contains( - 'Running for all packages that have uncommitted changes'), - ])); - - expect(command.plugins, unorderedEquals([packageA.path])); - }); - - test('multiple packages changed should test all the changed packages', - () async { - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: ''' -packages/a_package/lib/a_package.dart -packages/b_package/lib/src/foo.dart -'''), - ]; - final RepositoryPackage packageA = - createFakePackage('a_package', packagesDir); - final RepositoryPackage packageB = - createFakePackage('b_package', packagesDir); - createFakePackage('c_package', packagesDir); - await runCapturingPrint( - runner, ['sample', '--run-on-dirty-packages']); - - expect(command.plugins, - unorderedEquals([packageA.path, packageB.path])); - }); - - test('honors --exclude flag', () async { - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: ''' -packages/a_package/lib/a_package.dart -packages/b_package/lib/src/foo.dart -'''), - ]; - final RepositoryPackage packageA = - createFakePackage('a_package', packagesDir); - createFakePackage('b_package', packagesDir); - createFakePackage('c_package', packagesDir); - await runCapturingPrint(runner, [ - 'sample', - '--exclude=b_package', - '--run-on-dirty-packages' - ]); - - expect(command.plugins, unorderedEquals([packageA.path])); - }); - }); - }); - - group('--packages-for-branch', () { - test('only tests changed packages relative to the merge base on a branch', - () async { - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: 'packages/plugin1/plugin1.dart'), - ]; - processRunner.mockProcessesForExecutable['git-rev-parse'] = [ - MockProcess(stdout: 'a-branch'), - ]; - processRunner.mockProcessesForExecutable['git-merge-base'] = [ - MockProcess(exitCode: 1), // --is-ancestor check - MockProcess(stdout: 'abc123'), // finding merge base - ]; - final RepositoryPackage plugin1 = - createFakePlugin('plugin1', packagesDir); - createFakePlugin('plugin2', packagesDir); - - final List output = await runCapturingPrint( - runner, ['sample', '--packages-for-branch']); - - expect(command.plugins, unorderedEquals([plugin1.path])); - expect( - output, - containsAllInOrder([ - contains('--packages-for-branch: running on branch "a-branch"'), - contains( - 'Running for all packages that have diffs relative to "abc123"'), - ])); - // Ensure that it's diffing against the merge-base. - expect( - processRunner.recordedCalls, - contains( - const ProcessCall( - 'git-diff', ['--name-only', 'abc123', 'HEAD'], null), - )); - }); - - test('only tests changed packages relative to the previous commit on main', - () async { - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: 'packages/plugin1/plugin1.dart'), - ]; - processRunner.mockProcessesForExecutable['git-rev-parse'] = [ - MockProcess(stdout: 'main'), - ]; - final RepositoryPackage plugin1 = - createFakePlugin('plugin1', packagesDir); - createFakePlugin('plugin2', packagesDir); - - final List output = await runCapturingPrint( - runner, ['sample', '--packages-for-branch']); - - expect(command.plugins, unorderedEquals([plugin1.path])); - expect( - output, - containsAllInOrder([ - contains('--packages-for-branch: running on default branch.'), - contains( - '--packages-for-branch: using parent commit as the diff base'), - contains( - 'Running for all packages that have diffs relative to "HEAD~"'), - ])); - // Ensure that it's diffing against the prior commit. - expect( - processRunner.recordedCalls, - contains( - const ProcessCall( - 'git-diff', ['--name-only', 'HEAD~', 'HEAD'], null), - )); - }); - - test( - 'only tests changed packages relative to the previous commit if ' - 'running on a specific hash from main', () async { - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: 'packages/plugin1/plugin1.dart'), - ]; - processRunner.mockProcessesForExecutable['git-rev-parse'] = [ - MockProcess(stdout: 'HEAD'), - ]; - final RepositoryPackage plugin1 = - createFakePlugin('plugin1', packagesDir); - createFakePlugin('plugin2', packagesDir); - - final List output = await runCapturingPrint( - runner, ['sample', '--packages-for-branch']); - - expect(command.plugins, unorderedEquals([plugin1.path])); - expect( - output, - containsAllInOrder([ - contains( - '--packages-for-branch: running on a commit from default branch.'), - contains( - '--packages-for-branch: using parent commit as the diff base'), - contains( - 'Running for all packages that have diffs relative to "HEAD~"'), - ])); - // Ensure that it's diffing against the prior commit. - expect( - processRunner.recordedCalls, - contains( - const ProcessCall( - 'git-diff', ['--name-only', 'HEAD~', 'HEAD'], null), - )); - }); - - test( - 'only tests changed packages relative to the previous commit if ' - 'running on a specific hash from origin/main', () async { - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: 'packages/plugin1/plugin1.dart'), - ]; - processRunner.mockProcessesForExecutable['git-rev-parse'] = [ - MockProcess(stdout: 'HEAD'), - ]; - processRunner.mockProcessesForExecutable['git-merge-base'] = [ - MockProcess(exitCode: 128), // Fail with a non-1 exit code for 'main' - MockProcess(), // Succeed for the variant. - ]; - final RepositoryPackage plugin1 = - createFakePlugin('plugin1', packagesDir); - createFakePlugin('plugin2', packagesDir); - - final List output = await runCapturingPrint( - runner, ['sample', '--packages-for-branch']); - - expect(command.plugins, unorderedEquals([plugin1.path])); - expect( - output, - containsAllInOrder([ - contains( - '--packages-for-branch: running on a commit from default branch.'), - contains( - '--packages-for-branch: using parent commit as the diff base'), - contains( - 'Running for all packages that have diffs relative to "HEAD~"'), - ])); - // Ensure that it's diffing against the prior commit. - expect( - processRunner.recordedCalls, - contains( - const ProcessCall( - 'git-diff', ['--name-only', 'HEAD~', 'HEAD'], null), - )); - }); - - test( - 'only tests changed packages relative to the previous commit on master', - () async { - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: 'packages/plugin1/plugin1.dart'), - ]; - processRunner.mockProcessesForExecutable['git-rev-parse'] = [ - MockProcess(stdout: 'master'), - ]; - final RepositoryPackage plugin1 = - createFakePlugin('plugin1', packagesDir); - createFakePlugin('plugin2', packagesDir); - - final List output = await runCapturingPrint( - runner, ['sample', '--packages-for-branch']); - - expect(command.plugins, unorderedEquals([plugin1.path])); - expect( - output, - containsAllInOrder([ - contains('--packages-for-branch: running on default branch.'), - contains( - '--packages-for-branch: using parent commit as the diff base'), - contains( - 'Running for all packages that have diffs relative to "HEAD~"'), - ])); - // Ensure that it's diffing against the prior commit. - expect( - processRunner.recordedCalls, - contains( - const ProcessCall( - 'git-diff', ['--name-only', 'HEAD~', 'HEAD'], null), - )); - }); - - test('throws if getting the branch fails', () async { - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: 'packages/plugin1/plugin1.dart'), - ]; - processRunner.mockProcessesForExecutable['git-rev-parse'] = [ - MockProcess(exitCode: 1), - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['sample', '--packages-for-branch'], - errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Unable to determine branch'), - ])); - }); - }); - - group('sharding', () { - test('distributes evenly when evenly divisible', () async { - final List> expectedShards = - >[ - [ - createFakePackage('package1', packagesDir), - createFakePackage('package2', packagesDir), - createFakePackage('package3', packagesDir), - ], - [ - createFakePackage('package4', packagesDir), - createFakePackage('package5', packagesDir), - createFakePackage('package6', packagesDir), - ], - [ - createFakePackage('package7', packagesDir), - createFakePackage('package8', packagesDir), - createFakePackage('package9', packagesDir), - ], - ]; - - for (int i = 0; i < expectedShards.length; ++i) { - final SamplePackageCommand localCommand = SamplePackageCommand( - packagesDir, - processRunner: processRunner, - platform: mockPlatform, - gitDir: MockGitDir(), - ); - final CommandRunner localRunner = - CommandRunner('common_command', 'Shard testing'); - localRunner.addCommand(localCommand); - - await runCapturingPrint(localRunner, [ - 'sample', - '--shardIndex=$i', - '--shardCount=3', - ]); - expect( - localCommand.plugins, - unorderedEquals(expectedShards[i] - .map((RepositoryPackage package) => package.path) - .toList())); - } - }); - - test('distributes as evenly as possible when not evenly divisible', - () async { - final List> expectedShards = - >[ - [ - createFakePackage('package1', packagesDir), - createFakePackage('package2', packagesDir), - createFakePackage('package3', packagesDir), - ], - [ - createFakePackage('package4', packagesDir), - createFakePackage('package5', packagesDir), - createFakePackage('package6', packagesDir), - ], - [ - createFakePackage('package7', packagesDir), - createFakePackage('package8', packagesDir), - ], - ]; - - for (int i = 0; i < expectedShards.length; ++i) { - final SamplePackageCommand localCommand = SamplePackageCommand( - packagesDir, - processRunner: processRunner, - platform: mockPlatform, - gitDir: MockGitDir(), - ); - final CommandRunner localRunner = - CommandRunner('common_command', 'Shard testing'); - localRunner.addCommand(localCommand); - - await runCapturingPrint(localRunner, [ - 'sample', - '--shardIndex=$i', - '--shardCount=3', - ]); - expect( - localCommand.plugins, - unorderedEquals(expectedShards[i] - .map((RepositoryPackage package) => package.path) - .toList())); - } - }); - - // In CI (which is the use case for sharding) we often want to run muliple - // commands on the same set of packages, but the exclusion lists for those - // commands may be different. In those cases we still want all the commands - // to operate on a consistent set of plugins. - // - // E.g., some commands require running build-examples in a previous step; - // excluding some plugins from the later step shouldn't change what's tested - // in each shard, as it may no longer align with what was built. - test('counts excluded plugins when sharding', () async { - final List> expectedShards = - >[ - [ - createFakePackage('package1', packagesDir), - createFakePackage('package2', packagesDir), - createFakePackage('package3', packagesDir), - ], - [ - createFakePackage('package4', packagesDir), - createFakePackage('package5', packagesDir), - createFakePackage('package6', packagesDir), - ], - [ - createFakePackage('package7', packagesDir), - ], - ]; - // These would be in the last shard, but are excluded. - createFakePackage('package8', packagesDir); - createFakePackage('package9', packagesDir); - - for (int i = 0; i < expectedShards.length; ++i) { - final SamplePackageCommand localCommand = SamplePackageCommand( - packagesDir, - processRunner: processRunner, - platform: mockPlatform, - gitDir: MockGitDir(), - ); - final CommandRunner localRunner = - CommandRunner('common_command', 'Shard testing'); - localRunner.addCommand(localCommand); - - await runCapturingPrint(localRunner, [ - 'sample', - '--shardIndex=$i', - '--shardCount=3', - '--exclude=package8,package9', - ]); - expect( - localCommand.plugins, - unorderedEquals(expectedShards[i] - .map((RepositoryPackage package) => package.path) - .toList())); - } - }); - }); -} - -class SamplePackageCommand extends PackageCommand { - SamplePackageCommand( - Directory packagesDir, { - ProcessRunner processRunner = const ProcessRunner(), - Platform platform = const LocalPlatform(), - GitDir? gitDir, - this.includeSubpackages = false, - }) : super(packagesDir, - processRunner: processRunner, platform: platform, gitDir: gitDir); - - final List plugins = []; - - final bool includeSubpackages; - - @override - final String name = 'sample'; - - @override - final String description = 'sample command'; - - @override - Future run() async { - final Stream packages = includeSubpackages - ? getTargetPackagesAndSubpackages() - : getTargetPackages(); - await for (final PackageEnumerationEntry entry in packages) { - plugins.add(entry.package.path); - } - } -} diff --git a/script/tool/test/common/package_command_test.mocks.dart b/script/tool/test/common/package_command_test.mocks.dart deleted file mode 100644 index 79c5d4df1a8c..000000000000 --- a/script/tool/test/common/package_command_test.mocks.dart +++ /dev/null @@ -1,286 +0,0 @@ -// Mocks generated by Mockito 5.3.2 from annotations -// in flutter_plugin_tools/test/common/package_command_test.dart. -// Do not manually edit this file. - -// ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i6; -import 'dart:io' as _i4; - -import 'package:git/src/branch_reference.dart' as _i3; -import 'package:git/src/commit.dart' as _i2; -import 'package:git/src/commit_reference.dart' as _i8; -import 'package:git/src/git_dir.dart' as _i5; -import 'package:git/src/tag.dart' as _i7; -import 'package:git/src/tree_entry.dart' as _i9; -import 'package:mockito/mockito.dart' as _i1; - -// ignore_for_file: type=lint -// ignore_for_file: avoid_redundant_argument_values -// ignore_for_file: avoid_setters_without_getters -// ignore_for_file: comment_references -// ignore_for_file: implementation_imports -// ignore_for_file: invalid_use_of_visible_for_testing_member -// ignore_for_file: prefer_const_constructors -// ignore_for_file: unnecessary_parenthesis -// ignore_for_file: camel_case_types -// ignore_for_file: subtype_of_sealed_class - -class _FakeCommit_0 extends _i1.SmartFake implements _i2.Commit { - _FakeCommit_0( - Object parent, - Invocation parentInvocation, - ) : super( - parent, - parentInvocation, - ); -} - -class _FakeBranchReference_1 extends _i1.SmartFake - implements _i3.BranchReference { - _FakeBranchReference_1( - Object parent, - Invocation parentInvocation, - ) : super( - parent, - parentInvocation, - ); -} - -class _FakeProcessResult_2 extends _i1.SmartFake implements _i4.ProcessResult { - _FakeProcessResult_2( - Object parent, - Invocation parentInvocation, - ) : super( - parent, - parentInvocation, - ); -} - -/// A class which mocks [GitDir]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockGitDir extends _i1.Mock implements _i5.GitDir { - MockGitDir() { - _i1.throwOnMissingStub(this); - } - - @override - String get path => (super.noSuchMethod( - Invocation.getter(#path), - returnValue: '', - ) as String); - @override - _i6.Future commitCount([String? branchName = r'HEAD']) => - (super.noSuchMethod( - Invocation.method( - #commitCount, - [branchName], - ), - returnValue: _i6.Future.value(0), - ) as _i6.Future); - @override - _i6.Future<_i2.Commit> commitFromRevision(String? revision) => - (super.noSuchMethod( - Invocation.method( - #commitFromRevision, - [revision], - ), - returnValue: _i6.Future<_i2.Commit>.value(_FakeCommit_0( - this, - Invocation.method( - #commitFromRevision, - [revision], - ), - )), - ) as _i6.Future<_i2.Commit>); - @override - _i6.Future> commits([String? branchName = r'HEAD']) => - (super.noSuchMethod( - Invocation.method( - #commits, - [branchName], - ), - returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); - @override - _i6.Future<_i3.BranchReference?> branchReference(String? branchName) => - (super.noSuchMethod( - Invocation.method( - #branchReference, - [branchName], - ), - returnValue: _i6.Future<_i3.BranchReference?>.value(), - ) as _i6.Future<_i3.BranchReference?>); - @override - _i6.Future> branches() => (super.noSuchMethod( - Invocation.method( - #branches, - [], - ), - returnValue: _i6.Future>.value( - <_i3.BranchReference>[]), - ) as _i6.Future>); - @override - _i6.Stream<_i7.Tag> tags() => (super.noSuchMethod( - Invocation.method( - #tags, - [], - ), - returnValue: _i6.Stream<_i7.Tag>.empty(), - ) as _i6.Stream<_i7.Tag>); - @override - _i6.Future> showRef({ - bool? heads = false, - bool? tags = false, - }) => - (super.noSuchMethod( - Invocation.method( - #showRef, - [], - { - #heads: heads, - #tags: tags, - }, - ), - returnValue: _i6.Future>.value( - <_i8.CommitReference>[]), - ) as _i6.Future>); - @override - _i6.Future<_i3.BranchReference> currentBranch() => (super.noSuchMethod( - Invocation.method( - #currentBranch, - [], - ), - returnValue: - _i6.Future<_i3.BranchReference>.value(_FakeBranchReference_1( - this, - Invocation.method( - #currentBranch, - [], - ), - )), - ) as _i6.Future<_i3.BranchReference>); - @override - _i6.Future> lsTree( - String? treeish, { - bool? subTreesOnly = false, - String? path, - }) => - (super.noSuchMethod( - Invocation.method( - #lsTree, - [treeish], - { - #subTreesOnly: subTreesOnly, - #path: path, - }, - ), - returnValue: _i6.Future>.value(<_i9.TreeEntry>[]), - ) as _i6.Future>); - @override - _i6.Future createOrUpdateBranch( - String? branchName, - String? treeSha, - String? commitMessage, - ) => - (super.noSuchMethod( - Invocation.method( - #createOrUpdateBranch, - [ - branchName, - treeSha, - commitMessage, - ], - ), - returnValue: _i6.Future.value(), - ) as _i6.Future); - @override - _i6.Future commitTree( - String? treeSha, - String? commitMessage, { - List? parentCommitShas, - }) => - (super.noSuchMethod( - Invocation.method( - #commitTree, - [ - treeSha, - commitMessage, - ], - {#parentCommitShas: parentCommitShas}, - ), - returnValue: _i6.Future.value(''), - ) as _i6.Future); - @override - _i6.Future> writeObjects(List? paths) => - (super.noSuchMethod( - Invocation.method( - #writeObjects, - [paths], - ), - returnValue: _i6.Future>.value({}), - ) as _i6.Future>); - @override - _i6.Future<_i4.ProcessResult> runCommand( - Iterable? args, { - bool? throwOnError = true, - }) => - (super.noSuchMethod( - Invocation.method( - #runCommand, - [args], - {#throwOnError: throwOnError}, - ), - returnValue: _i6.Future<_i4.ProcessResult>.value(_FakeProcessResult_2( - this, - Invocation.method( - #runCommand, - [args], - {#throwOnError: throwOnError}, - ), - )), - ) as _i6.Future<_i4.ProcessResult>); - @override - _i6.Future isWorkingTreeClean() => (super.noSuchMethod( - Invocation.method( - #isWorkingTreeClean, - [], - ), - returnValue: _i6.Future.value(false), - ) as _i6.Future); - @override - _i6.Future<_i2.Commit?> updateBranch( - String? branchName, - _i6.Future Function(_i4.Directory)? populater, - String? commitMessage, - ) => - (super.noSuchMethod( - Invocation.method( - #updateBranch, - [ - branchName, - populater, - commitMessage, - ], - ), - returnValue: _i6.Future<_i2.Commit?>.value(), - ) as _i6.Future<_i2.Commit?>); - @override - _i6.Future<_i2.Commit?> updateBranchWithDirectoryContents( - String? branchName, - String? sourceDirectoryPath, - String? commitMessage, - ) => - (super.noSuchMethod( - Invocation.method( - #updateBranchWithDirectoryContents, - [ - branchName, - sourceDirectoryPath, - commitMessage, - ], - ), - returnValue: _i6.Future<_i2.Commit?>.value(), - ) as _i6.Future<_i2.Commit?>); -} diff --git a/script/tool/test/common/package_looping_command_test.dart b/script/tool/test/common/package_looping_command_test.dart deleted file mode 100644 index 34f346c62fe7..000000000000 --- a/script/tool/test/common/package_looping_command_test.dart +++ /dev/null @@ -1,949 +0,0 @@ -// Copyright 2013 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. - -import 'dart:async'; -import 'dart:io' as io; - -import 'package:args/command_runner.dart'; -import 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:flutter_plugin_tools/src/common/core.dart'; -import 'package:flutter_plugin_tools/src/common/package_looping_command.dart'; -import 'package:flutter_plugin_tools/src/common/process_runner.dart'; -import 'package:git/git.dart'; -import 'package:mockito/mockito.dart'; -import 'package:platform/platform.dart'; -import 'package:test/test.dart'; - -import '../mocks.dart'; -import '../util.dart'; -import 'package_command_test.mocks.dart'; - -// Constants for colorized output start and end. -const String _startElapsedTimeColor = '\x1B[90m'; -const String _startErrorColor = '\x1B[31m'; -const String _startHeadingColor = '\x1B[36m'; -const String _startSkipColor = '\x1B[90m'; -const String _startSkipWithWarningColor = '\x1B[93m'; -const String _startSuccessColor = '\x1B[32m'; -const String _startWarningColor = '\x1B[33m'; -const String _endColor = '\x1B[0m'; - -// The filename within a package containing warnings to log during runForPackage. -enum _ResultFileType { - /// A file containing errors to return. - errors, - - /// A file containing warnings that should be logged. - warns, - - /// A file indicating that the package should be skipped, and why. - skips, - - /// A file indicating that the package should throw. - throws, -} - -// The filename within a package containing errors to return from runForPackage. -const String _errorFile = 'errors'; -// The filename within a package indicating that it should be skipped. -const String _skipFile = 'skip'; -// The filename within a package containing warnings to log during runForPackage. -const String _warningFile = 'warnings'; -// The filename within a package indicating that it should throw. -const String _throwFile = 'throw'; - -/// Writes a file to [package] to control the behavior of -/// [TestPackageLoopingCommand] for that package. -void _addResultFile(RepositoryPackage package, _ResultFileType type, - {String? contents}) { - final File file = package.directory.childFile(_filenameForType(type)); - file.createSync(); - if (contents != null) { - file.writeAsStringSync(contents); - } -} - -String _filenameForType(_ResultFileType type) { - switch (type) { - case _ResultFileType.errors: - return _errorFile; - case _ResultFileType.warns: - return _warningFile; - case _ResultFileType.skips: - return _skipFile; - case _ResultFileType.throws: - return _throwFile; - } -} - -void main() { - late FileSystem fileSystem; - late MockPlatform mockPlatform; - late Directory packagesDir; - late Directory thirdPartyPackagesDir; - - setUp(() { - fileSystem = MemoryFileSystem(); - mockPlatform = MockPlatform(); - packagesDir = createPackagesDirectory(fileSystem: fileSystem); - thirdPartyPackagesDir = packagesDir.parent - .childDirectory('third_party') - .childDirectory('packages'); - }); - - /// Creates a TestPackageLoopingCommand instance that uses [gitDiffResponse] - /// for git diffs, and logs output to [printOutput]. - TestPackageLoopingCommand createTestCommand({ - String gitDiffResponse = '', - bool hasLongOutput = true, - PackageLoopingType packageLoopingType = PackageLoopingType.topLevelOnly, - bool failsDuringInit = false, - bool warnsDuringInit = false, - bool warnsDuringCleanup = false, - bool captureOutput = false, - String? customFailureListHeader, - String? customFailureListFooter, - }) { - // Set up the git diff response. - final MockGitDir gitDir = MockGitDir(); - when(gitDir.runCommand(any, throwOnError: anyNamed('throwOnError'))) - .thenAnswer((Invocation invocation) { - final List arguments = - invocation.positionalArguments[0]! as List; - final MockProcessResult mockProcessResult = MockProcessResult(); - if (arguments[0] == 'diff') { - when(mockProcessResult.stdout as String?) - .thenReturn(gitDiffResponse); - } - return Future.value(mockProcessResult); - }); - - return TestPackageLoopingCommand( - packagesDir, - platform: mockPlatform, - hasLongOutput: hasLongOutput, - packageLoopingType: packageLoopingType, - failsDuringInit: failsDuringInit, - warnsDuringInit: warnsDuringInit, - warnsDuringCleanup: warnsDuringCleanup, - customFailureListHeader: customFailureListHeader, - customFailureListFooter: customFailureListFooter, - captureOutput: captureOutput, - gitDir: gitDir, - ); - } - - /// Runs [command] with the given [arguments], and returns its output. - Future> runCommand( - TestPackageLoopingCommand command, { - List arguments = const [], - void Function(Error error)? errorHandler, - }) async { - late CommandRunner runner; - runner = CommandRunner('test_package_looping_command', - 'Test for base package looping functionality'); - runner.addCommand(command); - return runCapturingPrint( - runner, - [command.name, ...arguments], - errorHandler: errorHandler, - ); - } - - group('tool exit', () { - test('is handled during initializeRun', () async { - final TestPackageLoopingCommand command = - createTestCommand(failsDuringInit: true); - - expect(() => runCommand(command), throwsA(isA())); - }); - - test('does not stop looping on error', () async { - createFakePackage('package_a', packagesDir); - final RepositoryPackage failingPackage = - createFakePlugin('package_b', packagesDir); - createFakePackage('package_c', packagesDir); - _addResultFile(failingPackage, _ResultFileType.errors); - - final TestPackageLoopingCommand command = - createTestCommand(hasLongOutput: false); - Error? commandError; - final List output = - await runCommand(command, errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - '${_startHeadingColor}Running for package_a...$_endColor', - '${_startHeadingColor}Running for package_b...$_endColor', - '${_startHeadingColor}Running for package_c...$_endColor', - ])); - }); - - test('does not stop looping on exceptions', () async { - createFakePackage('package_a', packagesDir); - final RepositoryPackage failingPackage = - createFakePlugin('package_b', packagesDir); - createFakePackage('package_c', packagesDir); - _addResultFile(failingPackage, _ResultFileType.throws); - - final TestPackageLoopingCommand command = - createTestCommand(hasLongOutput: false); - Error? commandError; - final List output = - await runCommand(command, errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - '${_startHeadingColor}Running for package_a...$_endColor', - '${_startHeadingColor}Running for package_b...$_endColor', - '${_startHeadingColor}Running for package_c...$_endColor', - ])); - }); - }); - - group('package iteration', () { - test('includes plugins and packages', () async { - final RepositoryPackage plugin = - createFakePlugin('a_plugin', packagesDir); - final RepositoryPackage package = - createFakePackage('a_package', packagesDir); - - final TestPackageLoopingCommand command = createTestCommand(); - await runCommand(command); - - expect(command.checkedPackages, - unorderedEquals([plugin.path, package.path])); - }); - - test('includes third_party/packages', () async { - final RepositoryPackage package1 = - createFakePackage('a_package', packagesDir); - final RepositoryPackage package2 = - createFakePackage('another_package', thirdPartyPackagesDir); - - final TestPackageLoopingCommand command = createTestCommand(); - await runCommand(command); - - expect(command.checkedPackages, - unorderedEquals([package1.path, package2.path])); - }); - - test('includes all subpackages when requested', () async { - final RepositoryPackage plugin = createFakePlugin('a_plugin', packagesDir, - examples: ['example1', 'example2']); - final RepositoryPackage package = - createFakePackage('a_package', packagesDir); - final RepositoryPackage subPackage = createFakePackage( - 'sub_package', package.directory, - examples: []); - - final TestPackageLoopingCommand command = createTestCommand( - packageLoopingType: PackageLoopingType.includeAllSubpackages); - await runCommand(command); - - expect( - command.checkedPackages, - unorderedEquals([ - plugin.path, - getExampleDir(plugin).childDirectory('example1').path, - getExampleDir(plugin).childDirectory('example2').path, - package.path, - getExampleDir(package).path, - subPackage.path, - ])); - }); - - test('includes examples when requested', () async { - final RepositoryPackage plugin = createFakePlugin('a_plugin', packagesDir, - examples: ['example1', 'example2']); - final RepositoryPackage package = - createFakePackage('a_package', packagesDir); - final RepositoryPackage subPackage = - createFakePackage('sub_package', package.directory); - - final TestPackageLoopingCommand command = createTestCommand( - packageLoopingType: PackageLoopingType.includeExamples); - await runCommand(command); - - expect( - command.checkedPackages, - unorderedEquals([ - plugin.path, - getExampleDir(plugin).childDirectory('example1').path, - getExampleDir(plugin).childDirectory('example2').path, - package.path, - getExampleDir(package).path, - ])); - expect(command.checkedPackages, isNot(contains(subPackage.path))); - }); - - test('excludes subpackages when main package is excluded', () async { - final RepositoryPackage excluded = createFakePlugin( - 'a_plugin', packagesDir, - examples: ['example1', 'example2']); - final RepositoryPackage included = - createFakePackage('a_package', packagesDir); - final RepositoryPackage subpackage = - createFakePackage('sub_package', excluded.directory); - - final TestPackageLoopingCommand command = createTestCommand( - packageLoopingType: PackageLoopingType.includeAllSubpackages); - await runCommand(command, arguments: ['--exclude=a_plugin']); - - final Iterable examples = excluded.getExamples(); - - expect( - command.checkedPackages, - unorderedEquals([ - included.path, - getExampleDir(included).path, - ])); - expect(command.checkedPackages, isNot(contains(excluded.path))); - expect(examples.length, 2); - for (final RepositoryPackage example in examples) { - expect(command.checkedPackages, isNot(contains(example.path))); - } - expect(command.checkedPackages, isNot(contains(subpackage.path))); - }); - - test('excludes examples when main package is excluded', () async { - final RepositoryPackage excluded = createFakePlugin( - 'a_plugin', packagesDir, - examples: ['example1', 'example2']); - final RepositoryPackage included = - createFakePackage('a_package', packagesDir); - - final TestPackageLoopingCommand command = createTestCommand( - packageLoopingType: PackageLoopingType.includeExamples); - await runCommand(command, arguments: ['--exclude=a_plugin']); - - final Iterable examples = excluded.getExamples(); - - expect( - command.checkedPackages, - unorderedEquals([ - included.path, - getExampleDir(included).path, - ])); - expect(command.checkedPackages, isNot(contains(excluded.path))); - expect(examples.length, 2); - for (final RepositoryPackage example in examples) { - expect(command.checkedPackages, isNot(contains(example.path))); - } - }); - - test('skips unsupported Flutter versions when requested', () async { - final RepositoryPackage excluded = createFakePlugin( - 'a_plugin', packagesDir, - flutterConstraint: '>=2.10.0'); - final RepositoryPackage included = - createFakePackage('a_package', packagesDir); - - final TestPackageLoopingCommand command = createTestCommand( - packageLoopingType: PackageLoopingType.includeAllSubpackages, - hasLongOutput: false); - final List output = await runCommand(command, arguments: [ - '--skip-if-not-supporting-flutter-version=2.5.0' - ]); - - expect( - command.checkedPackages, - unorderedEquals([ - included.path, - getExampleDir(included).path, - ])); - expect(command.checkedPackages, isNot(contains(excluded.path))); - - expect( - output, - containsAllInOrder([ - '${_startHeadingColor}Running for a_package...$_endColor', - '${_startHeadingColor}Running for a_plugin...$_endColor', - '$_startSkipColor SKIPPING: Does not support Flutter 2.5.0$_endColor', - ])); - }); - - test('skips unsupported Dart versions when requested', () async { - final RepositoryPackage excluded = createFakePackage( - 'excluded_package', packagesDir, - dartConstraint: '>=2.17.0 <3.0.0'); - final RepositoryPackage included = - createFakePackage('a_package', packagesDir); - - final TestPackageLoopingCommand command = createTestCommand( - packageLoopingType: PackageLoopingType.includeAllSubpackages, - hasLongOutput: false); - final List output = await runCommand(command, - arguments: ['--skip-if-not-supporting-dart-version=2.14.0']); - - expect( - command.checkedPackages, - unorderedEquals([ - included.path, - getExampleDir(included).path, - ])); - expect(command.checkedPackages, isNot(contains(excluded.path))); - - expect( - output, - containsAllInOrder([ - '${_startHeadingColor}Running for a_package...$_endColor', - '${_startHeadingColor}Running for excluded_package...$_endColor', - '$_startSkipColor SKIPPING: Does not support Dart 2.14.0$_endColor', - ])); - }); - }); - - group('output', () { - test('has the expected package headers for long-form output', () async { - createFakePlugin('package_a', packagesDir); - createFakePackage('package_b', packagesDir); - - final TestPackageLoopingCommand command = createTestCommand(); - final List output = await runCommand(command); - - const String separator = - '============================================================'; - expect( - output, - containsAllInOrder([ - '$_startHeadingColor\n$separator\n|| Running for package_a\n$separator\n$_endColor', - '$_startHeadingColor\n$separator\n|| Running for package_b\n$separator\n$_endColor', - ])); - }); - - test('has the expected package headers for short-form output', () async { - createFakePlugin('package_a', packagesDir); - createFakePackage('package_b', packagesDir); - - final TestPackageLoopingCommand command = - createTestCommand(hasLongOutput: false); - final List output = await runCommand(command); - - expect( - output, - containsAllInOrder([ - '${_startHeadingColor}Running for package_a...$_endColor', - '${_startHeadingColor}Running for package_b...$_endColor', - ])); - }); - - test('prints timing info in long-form output when requested', () async { - createFakePlugin('package_a', packagesDir); - createFakePackage('package_b', packagesDir); - - final TestPackageLoopingCommand command = createTestCommand(); - final List output = - await runCommand(command, arguments: ['--log-timing']); - - const String separator = - '============================================================'; - expect( - output, - containsAllInOrder([ - '$_startHeadingColor\n$separator\n|| Running for package_a [@0:00]\n$separator\n$_endColor', - '$_startElapsedTimeColor\n[package_a completed in 0m 0s]$_endColor', - '$_startHeadingColor\n$separator\n|| Running for package_b [@0:00]\n$separator\n$_endColor', - '$_startElapsedTimeColor\n[package_b completed in 0m 0s]$_endColor', - ])); - }); - - test('prints timing info in short-form output when requested', () async { - createFakePlugin('package_a', packagesDir); - createFakePackage('package_b', packagesDir); - - final TestPackageLoopingCommand command = - createTestCommand(hasLongOutput: false); - final List output = - await runCommand(command, arguments: ['--log-timing']); - - expect( - output, - containsAllInOrder([ - '$_startHeadingColor[0:00] Running for package_a...$_endColor', - '$_startHeadingColor[0:00] Running for package_b...$_endColor', - ])); - // Short-form output should not include elapsed time. - expect(output, isNot(contains('[package_a completed in 0m 0s]'))); - }); - - test('shows the success message when nothing fails', () async { - createFakePackage('package_a', packagesDir); - createFakePackage('package_b', packagesDir); - - final TestPackageLoopingCommand command = - createTestCommand(hasLongOutput: false); - final List output = await runCommand(command); - - expect( - output, - containsAllInOrder([ - '\n', - '${_startSuccessColor}No issues found!$_endColor', - ])); - }); - - test('shows failure summaries when something fails without extra details', - () async { - createFakePackage('package_a', packagesDir); - final RepositoryPackage failingPackage1 = - createFakePlugin('package_b', packagesDir); - createFakePackage('package_c', packagesDir); - final RepositoryPackage failingPackage2 = - createFakePlugin('package_d', packagesDir); - _addResultFile(failingPackage1, _ResultFileType.errors); - _addResultFile(failingPackage2, _ResultFileType.errors); - - final TestPackageLoopingCommand command = - createTestCommand(hasLongOutput: false); - Error? commandError; - final List output = - await runCommand(command, errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - '\n', - '${_startErrorColor}The following packages had errors:$_endColor', - '$_startErrorColor package_b$_endColor', - '$_startErrorColor package_d$_endColor', - '${_startErrorColor}See above for full details.$_endColor', - ])); - }); - - test('uses custom summary header and footer if provided', () async { - createFakePackage('package_a', packagesDir); - final RepositoryPackage failingPackage1 = - createFakePlugin('package_b', packagesDir); - createFakePackage('package_c', packagesDir); - final RepositoryPackage failingPackage2 = - createFakePlugin('package_d', packagesDir); - _addResultFile(failingPackage1, _ResultFileType.errors); - _addResultFile(failingPackage2, _ResultFileType.errors); - - final TestPackageLoopingCommand command = createTestCommand( - hasLongOutput: false, - customFailureListHeader: 'This is a custom header', - customFailureListFooter: 'And a custom footer!'); - Error? commandError; - final List output = - await runCommand(command, errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - '\n', - '${_startErrorColor}This is a custom header$_endColor', - '$_startErrorColor package_b$_endColor', - '$_startErrorColor package_d$_endColor', - '${_startErrorColor}And a custom footer!$_endColor', - ])); - }); - - test('shows failure summaries when something fails with extra details', - () async { - createFakePackage('package_a', packagesDir); - final RepositoryPackage failingPackage1 = - createFakePlugin('package_b', packagesDir); - createFakePackage('package_c', packagesDir); - final RepositoryPackage failingPackage2 = - createFakePlugin('package_d', packagesDir); - _addResultFile(failingPackage1, _ResultFileType.errors, - contents: 'just one detail'); - _addResultFile(failingPackage2, _ResultFileType.errors, - contents: 'first detail\nsecond detail'); - - final TestPackageLoopingCommand command = - createTestCommand(hasLongOutput: false); - Error? commandError; - final List output = - await runCommand(command, errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - '\n', - '${_startErrorColor}The following packages had errors:$_endColor', - '$_startErrorColor package_b:\n just one detail$_endColor', - '$_startErrorColor package_d:\n first detail\n second detail$_endColor', - '${_startErrorColor}See above for full details.$_endColor', - ])); - }); - - test('is captured, not printed, when requested', () async { - createFakePlugin('package_a', packagesDir); - createFakePackage('package_b', packagesDir); - - final TestPackageLoopingCommand command = - createTestCommand(captureOutput: true); - final List output = await runCommand(command); - - expect(output, isEmpty); - - // None of the output should be colorized when captured. - const String separator = - '============================================================'; - expect( - command.capturedOutput, - containsAllInOrder([ - '\n$separator\n|| Running for package_a\n$separator\n', - '\n$separator\n|| Running for package_b\n$separator\n', - 'No issues found!', - ])); - }); - - test('logs skips', () async { - createFakePackage('package_a', packagesDir); - final RepositoryPackage skipPackage = - createFakePackage('package_b', packagesDir); - _addResultFile(skipPackage, _ResultFileType.skips, - contents: 'For a reason'); - - final TestPackageLoopingCommand command = - createTestCommand(hasLongOutput: false); - final List output = await runCommand(command); - - expect( - output, - containsAllInOrder([ - '${_startHeadingColor}Running for package_a...$_endColor', - '${_startHeadingColor}Running for package_b...$_endColor', - '$_startSkipColor SKIPPING: For a reason$_endColor', - ])); - }); - - test('logs exclusions', () async { - createFakePackage('package_a', packagesDir); - createFakePackage('package_b', packagesDir); - - final TestPackageLoopingCommand command = - createTestCommand(hasLongOutput: false); - final List output = - await runCommand(command, arguments: ['--exclude=package_b']); - - expect( - output, - containsAllInOrder([ - '${_startHeadingColor}Running for package_a...$_endColor', - '${_startSkipColor}Not running for package_b; excluded$_endColor', - ])); - }); - - test('logs warnings', () async { - final RepositoryPackage warnPackage = - createFakePackage('package_a', packagesDir); - _addResultFile(warnPackage, _ResultFileType.warns, - contents: 'Warning 1\nWarning 2'); - createFakePackage('package_b', packagesDir); - - final TestPackageLoopingCommand command = - createTestCommand(hasLongOutput: false); - final List output = await runCommand(command); - - expect( - output, - containsAllInOrder([ - '${_startHeadingColor}Running for package_a...$_endColor', - '${_startWarningColor}Warning 1$_endColor', - '${_startWarningColor}Warning 2$_endColor', - '${_startHeadingColor}Running for package_b...$_endColor', - ])); - }); - - test('logs unhandled exceptions as errors', () async { - createFakePackage('package_a', packagesDir); - final RepositoryPackage failingPackage = - createFakePlugin('package_b', packagesDir); - createFakePackage('package_c', packagesDir); - _addResultFile(failingPackage, _ResultFileType.throws); - - final TestPackageLoopingCommand command = - createTestCommand(hasLongOutput: false); - Error? commandError; - final List output = - await runCommand(command, errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - '${_startErrorColor}Exception: Uh-oh$_endColor', - '${_startErrorColor}The following packages had errors:$_endColor', - '$_startErrorColor package_b:\n Unhandled exception$_endColor', - ])); - }); - - test('prints run summary on success', () async { - final RepositoryPackage warnPackage1 = - createFakePackage('package_a', packagesDir); - _addResultFile(warnPackage1, _ResultFileType.warns, - contents: 'Warning 1\nWarning 2'); - - createFakePackage('package_b', packagesDir); - - final RepositoryPackage skipPackage = - createFakePackage('package_c', packagesDir); - _addResultFile(skipPackage, _ResultFileType.skips, - contents: 'For a reason'); - - final RepositoryPackage skipAndWarnPackage = - createFakePackage('package_d', packagesDir); - _addResultFile(skipAndWarnPackage, _ResultFileType.warns, - contents: 'Warning'); - _addResultFile(skipAndWarnPackage, _ResultFileType.skips, - contents: 'See warning'); - - final RepositoryPackage warnPackage2 = - createFakePackage('package_e', packagesDir); - _addResultFile(warnPackage2, _ResultFileType.warns, - contents: 'Warning 1\nWarning 2'); - - createFakePackage('package_f', packagesDir); - - final TestPackageLoopingCommand command = - createTestCommand(hasLongOutput: false); - final List output = await runCommand(command); - - expect( - output, - containsAllInOrder([ - '------------------------------------------------------------', - 'Ran for 4 package(s) (2 with warnings)', - 'Skipped 2 package(s) (1 with warnings)', - '\n', - '${_startSuccessColor}No issues found!$_endColor', - ])); - // The long-form summary should not be printed for short-form commands. - expect(output, isNot(contains('Run summary:'))); - expect(output, isNot(contains(contains('package a - ran')))); - }); - - test('counts exclusions as skips in run summary', () async { - createFakePackage('package_a', packagesDir); - - final TestPackageLoopingCommand command = - createTestCommand(hasLongOutput: false); - final List output = - await runCommand(command, arguments: ['--exclude=package_a']); - - expect( - output, - containsAllInOrder([ - '------------------------------------------------------------', - 'Skipped 1 package(s)', - '\n', - '${_startSuccessColor}No issues found!$_endColor', - ])); - }); - - test('prints long-form run summary for long-output commands', () async { - final RepositoryPackage warnPackage1 = - createFakePackage('package_a', packagesDir); - _addResultFile(warnPackage1, _ResultFileType.warns, - contents: 'Warning 1\nWarning 2'); - - createFakePackage('package_b', packagesDir); - - final RepositoryPackage skipPackage = - createFakePackage('package_c', packagesDir); - _addResultFile(skipPackage, _ResultFileType.skips, - contents: 'For a reason'); - - final RepositoryPackage skipAndWarnPackage = - createFakePackage('package_d', packagesDir); - _addResultFile(skipAndWarnPackage, _ResultFileType.warns, - contents: 'Warning'); - _addResultFile(skipAndWarnPackage, _ResultFileType.skips, - contents: 'See warning'); - - final RepositoryPackage warnPackage2 = - createFakePackage('package_e', packagesDir); - _addResultFile(warnPackage2, _ResultFileType.warns, - contents: 'Warning 1\nWarning 2'); - - createFakePackage('package_f', packagesDir); - - final TestPackageLoopingCommand command = createTestCommand(); - final List output = await runCommand(command); - - expect( - output, - containsAllInOrder([ - '------------------------------------------------------------', - 'Run overview:', - ' package_a - ${_startWarningColor}ran (with warning)$_endColor', - ' package_b - ${_startSuccessColor}ran$_endColor', - ' package_c - ${_startSkipColor}skipped$_endColor', - ' package_d - ${_startSkipWithWarningColor}skipped (with warning)$_endColor', - ' package_e - ${_startWarningColor}ran (with warning)$_endColor', - ' package_f - ${_startSuccessColor}ran$_endColor', - '', - 'Ran for 4 package(s) (2 with warnings)', - 'Skipped 2 package(s) (1 with warnings)', - '\n', - '${_startSuccessColor}No issues found!$_endColor', - ])); - }); - - test('prints exclusions as skips in long-form run summary', () async { - createFakePackage('package_a', packagesDir); - - final TestPackageLoopingCommand command = createTestCommand(); - final List output = - await runCommand(command, arguments: ['--exclude=package_a']); - - expect( - output, - containsAllInOrder([ - ' package_a - ${_startSkipColor}excluded$_endColor', - '', - 'Skipped 1 package(s)', - '\n', - '${_startSuccessColor}No issues found!$_endColor', - ])); - }); - - test('handles warnings outside of runForPackage', () async { - createFakePackage('package_a', packagesDir); - - final TestPackageLoopingCommand command = createTestCommand( - hasLongOutput: false, - warnsDuringCleanup: true, - warnsDuringInit: true, - ); - final List output = await runCommand(command); - - expect( - output, - containsAllInOrder([ - '${_startWarningColor}Warning during initializeRun$_endColor', - '${_startHeadingColor}Running for package_a...$_endColor', - '${_startWarningColor}Warning during completeRun$_endColor', - '------------------------------------------------------------', - 'Ran for 1 package(s)', - '2 warnings not associated with a package', - '\n', - '${_startSuccessColor}No issues found!$_endColor', - ])); - }); - }); -} - -class TestPackageLoopingCommand extends PackageLoopingCommand { - TestPackageLoopingCommand( - Directory packagesDir, { - required Platform platform, - this.hasLongOutput = true, - this.packageLoopingType = PackageLoopingType.topLevelOnly, - this.customFailureListHeader, - this.customFailureListFooter, - this.failsDuringInit = false, - this.warnsDuringInit = false, - this.warnsDuringCleanup = false, - this.captureOutput = false, - ProcessRunner processRunner = const ProcessRunner(), - GitDir? gitDir, - }) : super(packagesDir, - processRunner: processRunner, platform: platform, gitDir: gitDir); - - final List checkedPackages = []; - final List capturedOutput = []; - - final String? customFailureListHeader; - final String? customFailureListFooter; - - final bool failsDuringInit; - final bool warnsDuringInit; - final bool warnsDuringCleanup; - - @override - bool hasLongOutput; - - @override - PackageLoopingType packageLoopingType; - - @override - String get failureListHeader => - customFailureListHeader ?? super.failureListHeader; - - @override - String get failureListFooter => - customFailureListFooter ?? super.failureListFooter; - - @override - bool captureOutput; - - @override - final String name = 'loop-test'; - - @override - final String description = 'sample package looping command'; - - @override - Future initializeRun() async { - if (warnsDuringInit) { - logWarning('Warning during initializeRun'); - } - if (failsDuringInit) { - throw ToolExit(2); - } - } - - @override - Future runForPackage(RepositoryPackage package) async { - checkedPackages.add(package.path); - final File warningFile = package.directory.childFile(_warningFile); - if (warningFile.existsSync()) { - final List warnings = warningFile.readAsLinesSync(); - warnings.forEach(logWarning); - } - final File skipFile = package.directory.childFile(_skipFile); - if (skipFile.existsSync()) { - return PackageResult.skip(skipFile.readAsStringSync()); - } - final File errorFile = package.directory.childFile(_errorFile); - if (errorFile.existsSync()) { - return PackageResult.fail(errorFile.readAsLinesSync()); - } - final File throwFile = package.directory.childFile(_throwFile); - if (throwFile.existsSync()) { - throw Exception('Uh-oh'); - } - return PackageResult.success(); - } - - @override - Future completeRun() async { - if (warnsDuringInit) { - logWarning('Warning during completeRun'); - } - } - - @override - Future handleCapturedOutput(List output) async { - capturedOutput.addAll(output); - } -} - -class MockProcessResult extends Mock implements io.ProcessResult {} diff --git a/script/tool/test/common/package_state_utils_test.dart b/script/tool/test/common/package_state_utils_test.dart deleted file mode 100644 index 9b6429a084ce..000000000000 --- a/script/tool/test/common/package_state_utils_test.dart +++ /dev/null @@ -1,346 +0,0 @@ -// Copyright 2013 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. -import 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:flutter_plugin_tools/src/common/git_version_finder.dart'; -import 'package:flutter_plugin_tools/src/common/package_state_utils.dart'; -import 'package:test/fake.dart'; -import 'package:test/test.dart'; - -import '../util.dart'; - -void main() { - late FileSystem fileSystem; - late Directory packagesDir; - - setUp(() { - fileSystem = MemoryFileSystem(); - packagesDir = createPackagesDirectory(fileSystem: fileSystem); - }); - - group('checkPackageChangeState', () { - test('reports version change needed for code changes', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir); - - const List changedFiles = [ - 'packages/a_package/lib/plugin.dart', - ]; - - final PackageChangeState state = await checkPackageChangeState(package, - changedPaths: changedFiles, - relativePackagePath: 'packages/a_package'); - - expect(state.hasChanges, true); - expect(state.needsVersionChange, true); - expect(state.needsChangelogChange, true); - }); - - test('handles trailing slash on package path', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir); - - const List changedFiles = [ - 'packages/a_package/lib/plugin.dart', - ]; - - final PackageChangeState state = await checkPackageChangeState(package, - changedPaths: changedFiles, - relativePackagePath: 'packages/a_package/'); - - expect(state.hasChanges, true); - expect(state.needsVersionChange, true); - expect(state.needsChangelogChange, true); - expect(state.hasChangelogChange, false); - }); - - test('does not flag version- and changelog-change-exempt changes', - () async { - final RepositoryPackage package = - createFakePlugin('a_plugin', packagesDir); - - const List changedFiles = [ - 'packages/a_plugin/CHANGELOG.md', - // Analysis. - 'packages/a_plugin/example/android/lint-baseline.xml', - // Tests. - 'packages/a_plugin/example/android/src/androidTest/foo/bar/FooTest.java', - 'packages/a_plugin/example/ios/RunnerTests/Foo.m', - 'packages/a_plugin/example/ios/RunnerUITests/info.plist', - // Pigeon input. - 'packages/a_plugin/pigeons/messages.dart', - // Test scripts. - 'packages/a_plugin/run_tests.sh', - // Tools. - 'packages/a_plugin/tool/a_development_tool.dart', - // Example build files. - 'packages/a_plugin/example/android/build.gradle', - 'packages/a_plugin/example/android/gradle/wrapper/gradle-wrapper.properties', - 'packages/a_plugin/example/ios/Runner.xcodeproj/project.pbxproj', - 'packages/a_plugin/example/linux/flutter/CMakeLists.txt', - 'packages/a_plugin/example/macos/Runner.xcodeproj/project.pbxproj', - 'packages/a_plugin/example/windows/CMakeLists.txt', - 'packages/a_plugin/example/pubspec.yaml', - // Pigeon platform tests, which have an unusual structure. - 'packages/a_plugin/platform_tests/shared_test_plugin_code/lib/integration_tests.dart', - 'packages/a_plugin/platform_tests/test_plugin/windows/test_plugin.cpp', - ]; - - final PackageChangeState state = await checkPackageChangeState(package, - changedPaths: changedFiles, - relativePackagePath: 'packages/a_plugin/'); - - expect(state.hasChanges, true); - expect(state.needsVersionChange, false); - expect(state.needsChangelogChange, false); - expect(state.hasChangelogChange, true); - }); - - test('only considers a root "tool" folder to be special', () async { - final RepositoryPackage package = - createFakePlugin('a_plugin', packagesDir); - - const List changedFiles = [ - 'packages/a_plugin/lib/foo/tool/tool_thing.dart', - ]; - - final PackageChangeState state = await checkPackageChangeState(package, - changedPaths: changedFiles, - relativePackagePath: 'packages/a_plugin/'); - - expect(state.hasChanges, true); - expect(state.needsVersionChange, true); - expect(state.needsChangelogChange, true); - }); - - test('requires a version change for example/lib/main.dart', () async { - final RepositoryPackage package = createFakePlugin( - 'a_plugin', packagesDir, - extraFiles: ['example/lib/main.dart']); - - const List changedFiles = [ - 'packages/a_plugin/example/lib/main.dart', - ]; - - final PackageChangeState state = await checkPackageChangeState(package, - changedPaths: changedFiles, - relativePackagePath: 'packages/a_plugin/'); - - expect(state.hasChanges, true); - expect(state.needsVersionChange, true); - expect(state.needsChangelogChange, true); - }); - - test('requires a version change for example/main.dart', () async { - final RepositoryPackage package = createFakePlugin( - 'a_plugin', packagesDir, - extraFiles: ['example/main.dart']); - - const List changedFiles = [ - 'packages/a_plugin/example/main.dart', - ]; - - final PackageChangeState state = await checkPackageChangeState(package, - changedPaths: changedFiles, - relativePackagePath: 'packages/a_plugin/'); - - expect(state.hasChanges, true); - expect(state.needsVersionChange, true); - expect(state.needsChangelogChange, true); - }); - - test('requires a version change for example readme.md', () async { - final RepositoryPackage package = - createFakePlugin('a_plugin', packagesDir); - - const List changedFiles = [ - 'packages/a_plugin/example/README.md', - ]; - - final PackageChangeState state = await checkPackageChangeState(package, - changedPaths: changedFiles, - relativePackagePath: 'packages/a_plugin/'); - - expect(state.hasChanges, true); - expect(state.needsVersionChange, true); - expect(state.needsChangelogChange, true); - }); - - test('requires a version change for example/example.md', () async { - final RepositoryPackage package = createFakePlugin( - 'a_plugin', packagesDir, - extraFiles: ['example/example.md']); - - const List changedFiles = [ - 'packages/a_plugin/example/example.md', - ]; - - final PackageChangeState state = await checkPackageChangeState(package, - changedPaths: changedFiles, - relativePackagePath: 'packages/a_plugin/'); - - expect(state.hasChanges, true); - expect(state.needsVersionChange, true); - expect(state.needsChangelogChange, true); - }); - - test( - 'requires a changelog change but no version change for ' - 'lower-priority examples when example.md is present', () async { - final RepositoryPackage package = createFakePlugin( - 'a_plugin', packagesDir, - extraFiles: ['example/example.md']); - - const List changedFiles = [ - 'packages/a_plugin/example/lib/main.dart', - 'packages/a_plugin/example/main.dart', - 'packages/a_plugin/example/README.md', - ]; - - final PackageChangeState state = await checkPackageChangeState(package, - changedPaths: changedFiles, - relativePackagePath: 'packages/a_plugin/'); - - expect(state.hasChanges, true); - expect(state.needsVersionChange, false); - expect(state.needsChangelogChange, true); - }); - - test( - 'requires a changelog change but no version change for README.md when ' - 'code example is present', () async { - final RepositoryPackage package = createFakePlugin( - 'a_plugin', packagesDir, - extraFiles: ['example/lib/main.dart']); - - const List changedFiles = [ - 'packages/a_plugin/example/README.md', - ]; - - final PackageChangeState state = await checkPackageChangeState(package, - changedPaths: changedFiles, - relativePackagePath: 'packages/a_plugin/'); - - expect(state.hasChanges, true); - expect(state.needsVersionChange, false); - expect(state.needsChangelogChange, true); - }); - - test( - 'does not requires changelog or version change for build.gradle ' - 'test-dependency-only changes', () async { - final RepositoryPackage package = - createFakePlugin('a_plugin', packagesDir); - - const List changedFiles = [ - 'packages/a_plugin/android/build.gradle', - ]; - - final GitVersionFinder git = FakeGitVersionFinder(>{ - 'packages/a_plugin/android/build.gradle': [ - "- androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'", - "- testImplementation 'junit:junit:4.10.0'", - "+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'", - "+ testImplementation 'junit:junit:4.13.2'", - ] - }); - - final PackageChangeState state = await checkPackageChangeState(package, - changedPaths: changedFiles, - relativePackagePath: 'packages/a_plugin/', - git: git); - - expect(state.hasChanges, true); - expect(state.needsVersionChange, false); - expect(state.needsChangelogChange, false); - }); - - test('requires changelog or version change for other build.gradle changes', - () async { - final RepositoryPackage package = - createFakePlugin('a_plugin', packagesDir); - - const List changedFiles = [ - 'packages/a_plugin/android/build.gradle', - ]; - - final GitVersionFinder git = FakeGitVersionFinder(>{ - 'packages/a_plugin/android/build.gradle': [ - "- androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'", - "- testImplementation 'junit:junit:4.10.0'", - "+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'", - "+ testImplementation 'junit:junit:4.13.2'", - "- implementation 'com.google.android.gms:play-services-maps:18.0.0'", - "+ implementation 'com.google.android.gms:play-services-maps:18.0.2'", - ] - }); - - final PackageChangeState state = await checkPackageChangeState(package, - changedPaths: changedFiles, - relativePackagePath: 'packages/a_plugin/', - git: git); - - expect(state.hasChanges, true); - expect(state.needsVersionChange, true); - expect(state.needsChangelogChange, true); - }); - - test( - 'requires changelog or version change if build.gradle diffs cannot ' - 'be checked', () async { - final RepositoryPackage package = - createFakePlugin('a_plugin', packagesDir); - - const List changedFiles = [ - 'packages/a_plugin/android/build.gradle', - ]; - - final PackageChangeState state = await checkPackageChangeState(package, - changedPaths: changedFiles, - relativePackagePath: 'packages/a_plugin/'); - - expect(state.hasChanges, true); - expect(state.needsVersionChange, true); - expect(state.needsChangelogChange, true); - }); - - test( - 'requires changelog or version change if build.gradle diffs cannot ' - 'be determined', () async { - final RepositoryPackage package = - createFakePlugin('a_plugin', packagesDir); - - const List changedFiles = [ - 'packages/a_plugin/android/build.gradle', - ]; - - final GitVersionFinder git = FakeGitVersionFinder(>{ - 'packages/a_plugin/android/build.gradle': [] - }); - - final PackageChangeState state = await checkPackageChangeState(package, - changedPaths: changedFiles, - relativePackagePath: 'packages/a_plugin/', - git: git); - - expect(state.hasChanges, true); - expect(state.needsVersionChange, true); - expect(state.needsChangelogChange, true); - }); - }); -} - -class FakeGitVersionFinder extends Fake implements GitVersionFinder { - FakeGitVersionFinder(this.fileDiffs); - - final Map> fileDiffs; - - @override - Future> getDiffContents({ - String? targetPath, - bool includeUncommitted = false, - }) async { - return fileDiffs[targetPath]!; - } -} diff --git a/script/tool/test/common/plugin_utils_test.dart b/script/tool/test/common/plugin_utils_test.dart deleted file mode 100644 index 415b1db8932a..000000000000 --- a/script/tool/test/common/plugin_utils_test.dart +++ /dev/null @@ -1,256 +0,0 @@ -// Copyright 2013 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. - -import 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:flutter_plugin_tools/src/common/core.dart'; -import 'package:flutter_plugin_tools/src/common/plugin_utils.dart'; -import 'package:test/test.dart'; - -import '../util.dart'; - -void main() { - late FileSystem fileSystem; - late Directory packagesDir; - - setUp(() { - fileSystem = MemoryFileSystem(); - packagesDir = createPackagesDirectory(fileSystem: fileSystem); - }); - - group('pluginSupportsPlatform', () { - test('no platforms', () async { - final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir); - - expect(pluginSupportsPlatform(platformAndroid, plugin), isFalse); - expect(pluginSupportsPlatform(platformIOS, plugin), isFalse); - expect(pluginSupportsPlatform(platformLinux, plugin), isFalse); - expect(pluginSupportsPlatform(platformMacOS, plugin), isFalse); - expect(pluginSupportsPlatform(platformWeb, plugin), isFalse); - expect(pluginSupportsPlatform(platformWindows, plugin), isFalse); - }); - - test('all platforms', () async { - final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir, - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline), - platformIOS: const PlatformDetails(PlatformSupport.inline), - platformLinux: const PlatformDetails(PlatformSupport.inline), - platformMacOS: const PlatformDetails(PlatformSupport.inline), - platformWeb: const PlatformDetails(PlatformSupport.inline), - platformWindows: const PlatformDetails(PlatformSupport.inline), - }); - - expect(pluginSupportsPlatform(platformAndroid, plugin), isTrue); - expect(pluginSupportsPlatform(platformIOS, plugin), isTrue); - expect(pluginSupportsPlatform(platformLinux, plugin), isTrue); - expect(pluginSupportsPlatform(platformMacOS, plugin), isTrue); - expect(pluginSupportsPlatform(platformWeb, plugin), isTrue); - expect(pluginSupportsPlatform(platformWindows, plugin), isTrue); - }); - - test('some platforms', () async { - final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir, - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline), - platformLinux: const PlatformDetails(PlatformSupport.inline), - platformWeb: const PlatformDetails(PlatformSupport.inline), - }); - - expect(pluginSupportsPlatform(platformAndroid, plugin), isTrue); - expect(pluginSupportsPlatform(platformIOS, plugin), isFalse); - expect(pluginSupportsPlatform(platformLinux, plugin), isTrue); - expect(pluginSupportsPlatform(platformMacOS, plugin), isFalse); - expect(pluginSupportsPlatform(platformWeb, plugin), isTrue); - expect(pluginSupportsPlatform(platformWindows, plugin), isFalse); - }); - - test('inline plugins are only detected as inline', () async { - final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir, - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline), - platformIOS: const PlatformDetails(PlatformSupport.inline), - platformLinux: const PlatformDetails(PlatformSupport.inline), - platformMacOS: const PlatformDetails(PlatformSupport.inline), - platformWeb: const PlatformDetails(PlatformSupport.inline), - platformWindows: const PlatformDetails(PlatformSupport.inline), - }); - - expect( - pluginSupportsPlatform(platformAndroid, plugin, - requiredMode: PlatformSupport.inline), - isTrue); - expect( - pluginSupportsPlatform(platformAndroid, plugin, - requiredMode: PlatformSupport.federated), - isFalse); - expect( - pluginSupportsPlatform(platformIOS, plugin, - requiredMode: PlatformSupport.inline), - isTrue); - expect( - pluginSupportsPlatform(platformIOS, plugin, - requiredMode: PlatformSupport.federated), - isFalse); - expect( - pluginSupportsPlatform(platformLinux, plugin, - requiredMode: PlatformSupport.inline), - isTrue); - expect( - pluginSupportsPlatform(platformLinux, plugin, - requiredMode: PlatformSupport.federated), - isFalse); - expect( - pluginSupportsPlatform(platformMacOS, plugin, - requiredMode: PlatformSupport.inline), - isTrue); - expect( - pluginSupportsPlatform(platformMacOS, plugin, - requiredMode: PlatformSupport.federated), - isFalse); - expect( - pluginSupportsPlatform(platformWeb, plugin, - requiredMode: PlatformSupport.inline), - isTrue); - expect( - pluginSupportsPlatform(platformWeb, plugin, - requiredMode: PlatformSupport.federated), - isFalse); - expect( - pluginSupportsPlatform(platformWindows, plugin, - requiredMode: PlatformSupport.inline), - isTrue); - expect( - pluginSupportsPlatform(platformWindows, plugin, - requiredMode: PlatformSupport.federated), - isFalse); - }); - - test('federated plugins are only detected as federated', () async { - final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir, - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.federated), - platformIOS: const PlatformDetails(PlatformSupport.federated), - platformLinux: const PlatformDetails(PlatformSupport.federated), - platformMacOS: const PlatformDetails(PlatformSupport.federated), - platformWeb: const PlatformDetails(PlatformSupport.federated), - platformWindows: const PlatformDetails(PlatformSupport.federated), - }); - - expect( - pluginSupportsPlatform(platformAndroid, plugin, - requiredMode: PlatformSupport.federated), - isTrue); - expect( - pluginSupportsPlatform(platformAndroid, plugin, - requiredMode: PlatformSupport.inline), - isFalse); - expect( - pluginSupportsPlatform(platformIOS, plugin, - requiredMode: PlatformSupport.federated), - isTrue); - expect( - pluginSupportsPlatform(platformIOS, plugin, - requiredMode: PlatformSupport.inline), - isFalse); - expect( - pluginSupportsPlatform(platformLinux, plugin, - requiredMode: PlatformSupport.federated), - isTrue); - expect( - pluginSupportsPlatform(platformLinux, plugin, - requiredMode: PlatformSupport.inline), - isFalse); - expect( - pluginSupportsPlatform(platformMacOS, plugin, - requiredMode: PlatformSupport.federated), - isTrue); - expect( - pluginSupportsPlatform(platformMacOS, plugin, - requiredMode: PlatformSupport.inline), - isFalse); - expect( - pluginSupportsPlatform(platformWeb, plugin, - requiredMode: PlatformSupport.federated), - isTrue); - expect( - pluginSupportsPlatform(platformWeb, plugin, - requiredMode: PlatformSupport.inline), - isFalse); - expect( - pluginSupportsPlatform(platformWindows, plugin, - requiredMode: PlatformSupport.federated), - isTrue); - expect( - pluginSupportsPlatform(platformWindows, plugin, - requiredMode: PlatformSupport.inline), - isFalse); - }); - }); - - group('pluginHasNativeCodeForPlatform', () { - test('returns false for web', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', - packagesDir, - platformSupport: { - platformWeb: const PlatformDetails(PlatformSupport.inline), - }, - ); - - expect(pluginHasNativeCodeForPlatform(platformWeb, plugin), isFalse); - }); - - test('returns false for a native-only plugin', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', - packagesDir, - platformSupport: { - platformLinux: const PlatformDetails(PlatformSupport.inline), - platformMacOS: const PlatformDetails(PlatformSupport.inline), - platformWindows: const PlatformDetails(PlatformSupport.inline), - }, - ); - - expect(pluginHasNativeCodeForPlatform(platformLinux, plugin), isTrue); - expect(pluginHasNativeCodeForPlatform(platformMacOS, plugin), isTrue); - expect(pluginHasNativeCodeForPlatform(platformWindows, plugin), isTrue); - }); - - test('returns true for a native+Dart plugin', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', - packagesDir, - platformSupport: { - platformLinux: const PlatformDetails(PlatformSupport.inline, hasDartCode: true), - platformMacOS: const PlatformDetails(PlatformSupport.inline, hasDartCode: true), - platformWindows: const PlatformDetails(PlatformSupport.inline, hasDartCode: true), - }, - ); - - expect(pluginHasNativeCodeForPlatform(platformLinux, plugin), isTrue); - expect(pluginHasNativeCodeForPlatform(platformMacOS, plugin), isTrue); - expect(pluginHasNativeCodeForPlatform(platformWindows, plugin), isTrue); - }); - - test('returns false for a Dart-only plugin', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', - packagesDir, - platformSupport: { - platformLinux: const PlatformDetails(PlatformSupport.inline, - hasNativeCode: false, hasDartCode: true), - platformMacOS: const PlatformDetails(PlatformSupport.inline, - hasNativeCode: false, hasDartCode: true), - platformWindows: const PlatformDetails(PlatformSupport.inline, - hasNativeCode: false, hasDartCode: true), - }, - ); - - expect(pluginHasNativeCodeForPlatform(platformLinux, plugin), isFalse); - expect(pluginHasNativeCodeForPlatform(platformMacOS, plugin), isFalse); - expect(pluginHasNativeCodeForPlatform(platformWindows, plugin), isFalse); - }); - }); -} diff --git a/script/tool/test/common/pub_version_finder_test.dart b/script/tool/test/common/pub_version_finder_test.dart deleted file mode 100644 index 1692cf214abe..000000000000 --- a/script/tool/test/common/pub_version_finder_test.dart +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright 2013 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. - -import 'dart:convert'; -import 'dart:io'; - -import 'package:flutter_plugin_tools/src/common/pub_version_finder.dart'; -import 'package:http/http.dart' as http; -import 'package:http/testing.dart'; -import 'package:mockito/mockito.dart'; -import 'package:pub_semver/pub_semver.dart'; -import 'package:test/test.dart'; - -void main() { - test('Package does not exist.', () async { - final MockClient mockClient = MockClient((http.Request request) async { - return http.Response('', 404); - }); - final PubVersionFinder finder = PubVersionFinder(httpClient: mockClient); - final PubVersionFinderResponse response = - await finder.getPackageVersion(packageName: 'some_package'); - - expect(response.versions, isEmpty); - expect(response.result, PubVersionFinderResult.noPackageFound); - expect(response.httpResponse.statusCode, 404); - expect(response.httpResponse.body, ''); - }); - - test('HTTP error when getting versions from pub', () async { - final MockClient mockClient = MockClient((http.Request request) async { - return http.Response('', 400); - }); - final PubVersionFinder finder = PubVersionFinder(httpClient: mockClient); - final PubVersionFinderResponse response = - await finder.getPackageVersion(packageName: 'some_package'); - - expect(response.versions, isEmpty); - expect(response.result, PubVersionFinderResult.fail); - expect(response.httpResponse.statusCode, 400); - expect(response.httpResponse.body, ''); - }); - - test('Get a correct list of versions when http response is OK.', () async { - const Map httpResponse = { - 'name': 'some_package', - 'versions': [ - '0.0.1', - '0.0.2', - '0.0.2+2', - '0.1.1', - '0.0.1+1', - '0.1.0', - '0.2.0', - '0.1.0+1', - '0.0.2+1', - '2.0.0', - '1.2.0', - '1.0.0', - ], - }; - final MockClient mockClient = MockClient((http.Request request) async { - return http.Response(json.encode(httpResponse), 200); - }); - final PubVersionFinder finder = PubVersionFinder(httpClient: mockClient); - final PubVersionFinderResponse response = - await finder.getPackageVersion(packageName: 'some_package'); - - expect(response.versions, [ - Version.parse('2.0.0'), - Version.parse('1.2.0'), - Version.parse('1.0.0'), - Version.parse('0.2.0'), - Version.parse('0.1.1'), - Version.parse('0.1.0+1'), - Version.parse('0.1.0'), - Version.parse('0.0.2+2'), - Version.parse('0.0.2+1'), - Version.parse('0.0.2'), - Version.parse('0.0.1+1'), - Version.parse('0.0.1'), - ]); - expect(response.result, PubVersionFinderResult.success); - expect(response.httpResponse.statusCode, 200); - expect(response.httpResponse.body, json.encode(httpResponse)); - }); -} - -class MockProcessResult extends Mock implements ProcessResult {} diff --git a/script/tool/test/common/repository_package_test.dart b/script/tool/test/common/repository_package_test.dart deleted file mode 100644 index db519c008233..000000000000 --- a/script/tool/test/common/repository_package_test.dart +++ /dev/null @@ -1,220 +0,0 @@ -// Copyright 2013 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. - -import 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:test/test.dart'; - -import '../util.dart'; - -void main() { - late FileSystem fileSystem; - late Directory packagesDir; - - setUp(() { - fileSystem = MemoryFileSystem(); - packagesDir = createPackagesDirectory(fileSystem: fileSystem); - }); - - group('displayName', () { - test('prints packageDir-relative paths by default', () async { - expect( - RepositoryPackage(packagesDir.childDirectory('foo')).displayName, - 'foo', - ); - expect( - RepositoryPackage(packagesDir - .childDirectory('foo') - .childDirectory('bar') - .childDirectory('baz')) - .displayName, - 'foo/bar/baz', - ); - }); - - test('handles third_party/packages/', () async { - expect( - RepositoryPackage(packagesDir.parent - .childDirectory('third_party') - .childDirectory('packages') - .childDirectory('foo') - .childDirectory('bar') - .childDirectory('baz')) - .displayName, - 'foo/bar/baz', - ); - }); - - test('always uses Posix-style paths', () async { - final Directory windowsPackagesDir = createPackagesDirectory( - fileSystem: MemoryFileSystem(style: FileSystemStyle.windows)); - - expect( - RepositoryPackage(windowsPackagesDir.childDirectory('foo')).displayName, - 'foo', - ); - expect( - RepositoryPackage(windowsPackagesDir - .childDirectory('foo') - .childDirectory('bar') - .childDirectory('baz')) - .displayName, - 'foo/bar/baz', - ); - }); - - test('elides group name in grouped federated plugin structure', () async { - expect( - RepositoryPackage(packagesDir - .childDirectory('a_plugin') - .childDirectory('a_plugin_platform_interface')) - .displayName, - 'a_plugin_platform_interface', - ); - expect( - RepositoryPackage(packagesDir - .childDirectory('a_plugin') - .childDirectory('a_plugin_platform_web')) - .displayName, - 'a_plugin_platform_web', - ); - }); - - // The app-facing package doesn't get elided to avoid potential confusion - // with the group folder itself. - test('does not elide group name for app-facing packages', () async { - expect( - RepositoryPackage(packagesDir - .childDirectory('a_plugin') - .childDirectory('a_plugin')) - .displayName, - 'a_plugin/a_plugin', - ); - }); - }); - - group('getExamples', () { - test('handles a single Flutter example', () async { - final RepositoryPackage plugin = - createFakePlugin('a_plugin', packagesDir); - - final List examples = plugin.getExamples().toList(); - - expect(examples.length, 1); - expect(examples[0].path, getExampleDir(plugin).path); - }); - - test('handles multiple Flutter examples', () async { - final RepositoryPackage plugin = createFakePlugin('a_plugin', packagesDir, - examples: ['example1', 'example2']); - - final List examples = plugin.getExamples().toList(); - - expect(examples.length, 2); - expect(examples[0].path, - getExampleDir(plugin).childDirectory('example1').path); - expect(examples[1].path, - getExampleDir(plugin).childDirectory('example2').path); - }); - - test('handles a single non-Flutter example', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir); - - final List examples = package.getExamples().toList(); - - expect(examples.length, 1); - expect(examples[0].path, getExampleDir(package).path); - }); - - test('handles multiple non-Flutter examples', () async { - final RepositoryPackage package = createFakePackage( - 'a_package', packagesDir, - examples: ['example1', 'example2']); - - final List examples = package.getExamples().toList(); - - expect(examples.length, 2); - expect(examples[0].path, - getExampleDir(package).childDirectory('example1').path); - expect(examples[1].path, - getExampleDir(package).childDirectory('example2').path); - }); - }); - - group('federated plugin queries', () { - test('all return false for a simple plugin', () { - final RepositoryPackage plugin = - createFakePlugin('a_plugin', packagesDir); - expect(plugin.isFederated, false); - expect(plugin.isAppFacing, false); - expect(plugin.isPlatformInterface, false); - expect(plugin.isFederated, false); - }); - - test('handle app-facing packages', () { - final RepositoryPackage plugin = - createFakePlugin('a_plugin', packagesDir.childDirectory('a_plugin')); - expect(plugin.isFederated, true); - expect(plugin.isAppFacing, true); - expect(plugin.isPlatformInterface, false); - expect(plugin.isPlatformImplementation, false); - }); - - test('handle platform interface packages', () { - final RepositoryPackage plugin = createFakePlugin( - 'a_plugin_platform_interface', - packagesDir.childDirectory('a_plugin')); - expect(plugin.isFederated, true); - expect(plugin.isAppFacing, false); - expect(plugin.isPlatformInterface, true); - expect(plugin.isPlatformImplementation, false); - }); - - test('handle platform implementation packages', () { - // A platform interface can end with anything, not just one of the known - // platform names, because of cases like webview_flutter_wkwebview. - final RepositoryPackage plugin = createFakePlugin( - 'a_plugin_foo', packagesDir.childDirectory('a_plugin')); - expect(plugin.isFederated, true); - expect(plugin.isAppFacing, false); - expect(plugin.isPlatformInterface, false); - expect(plugin.isPlatformImplementation, true); - }); - }); - - group('pubspec', () { - test('file', () async { - final RepositoryPackage plugin = - createFakePlugin('a_plugin', packagesDir); - - final File pubspecFile = plugin.pubspecFile; - - expect(pubspecFile.path, plugin.directory.childFile('pubspec.yaml').path); - }); - - test('parsing', () async { - final RepositoryPackage plugin = createFakePlugin('a_plugin', packagesDir, - examples: ['example1', 'example2']); - - final Pubspec pubspec = plugin.parsePubspec(); - - expect(pubspec.name, 'a_plugin'); - }); - }); - - group('requiresFlutter', () { - test('returns true for Flutter package', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir, isFlutter: true); - expect(package.requiresFlutter(), true); - }); - - test('returns false for non-Flutter package', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir); - expect(package.requiresFlutter(), false); - }); - }); -} diff --git a/script/tool/test/common/xcode_test.dart b/script/tool/test/common/xcode_test.dart deleted file mode 100644 index 259d8ea36cd2..000000000000 --- a/script/tool/test/common/xcode_test.dart +++ /dev/null @@ -1,406 +0,0 @@ -// Copyright 2013 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. - -import 'dart:convert'; -import 'dart:io' as io; - -import 'package:file/file.dart'; -import 'package:file/local.dart'; -import 'package:flutter_plugin_tools/src/common/xcode.dart'; -import 'package:test/test.dart'; - -import '../mocks.dart'; -import '../util.dart'; - -void main() { - late RecordingProcessRunner processRunner; - late Xcode xcode; - - setUp(() { - processRunner = RecordingProcessRunner(); - xcode = Xcode(processRunner: processRunner); - }); - - group('findBestAvailableIphoneSimulator', () { - test('finds the newest device', () async { - const String expectedDeviceId = '1E76A0FD-38AC-4537-A989-EA639D7D012A'; - // Note: This uses `dynamic` deliberately, and should not be updated to - // Object, in order to ensure that the code correctly handles this return - // type from JSON decoding. - final Map devices = { - 'runtimes': >[ - { - 'bundlePath': - '/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 13.0.simruntime', - 'buildversion': '17A577', - 'runtimeRoot': - '/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 13.0.simruntime/Contents/Resources/RuntimeRoot', - 'identifier': 'com.apple.CoreSimulator.SimRuntime.iOS-13-0', - 'version': '13.0', - 'isAvailable': true, - 'name': 'iOS 13.0' - }, - { - 'bundlePath': - '/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 13.4.simruntime', - 'buildversion': '17L255', - 'runtimeRoot': - '/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 13.4.simruntime/Contents/Resources/RuntimeRoot', - 'identifier': 'com.apple.CoreSimulator.SimRuntime.iOS-13-4', - 'version': '13.4', - 'isAvailable': true, - 'name': 'iOS 13.4' - }, - { - 'bundlePath': - '/Applications/Xcode_11_7.app/Contents/Developer/Platforms/WatchOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/watchOS.simruntime', - 'buildversion': '17T531', - 'runtimeRoot': - '/Applications/Xcode_11_7.app/Contents/Developer/Platforms/WatchOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/watchOS.simruntime/Contents/Resources/RuntimeRoot', - 'identifier': 'com.apple.CoreSimulator.SimRuntime.watchOS-6-2', - 'version': '6.2.1', - 'isAvailable': true, - 'name': 'watchOS 6.2' - } - ], - 'devices': { - 'com.apple.CoreSimulator.SimRuntime.iOS-13-4': >[ - { - 'dataPath': - '/Users/xxx/Library/Developer/CoreSimulator/Devices/2706BBEB-1E01-403E-A8E9-70E8E5A24774/data', - 'logPath': - '/Users/xxx/Library/Logs/CoreSimulator/2706BBEB-1E01-403E-A8E9-70E8E5A24774', - 'udid': '2706BBEB-1E01-403E-A8E9-70E8E5A24774', - 'isAvailable': true, - 'deviceTypeIdentifier': - 'com.apple.CoreSimulator.SimDeviceType.iPhone-8', - 'state': 'Shutdown', - 'name': 'iPhone 8' - }, - { - 'dataPath': - '/Users/xxx/Library/Developer/CoreSimulator/Devices/1E76A0FD-38AC-4537-A989-EA639D7D012A/data', - 'logPath': - '/Users/xxx/Library/Logs/CoreSimulator/1E76A0FD-38AC-4537-A989-EA639D7D012A', - 'udid': expectedDeviceId, - 'isAvailable': true, - 'deviceTypeIdentifier': - 'com.apple.CoreSimulator.SimDeviceType.iPhone-8-Plus', - 'state': 'Shutdown', - 'name': 'iPhone 8 Plus' - } - ] - } - }; - - processRunner.mockProcessesForExecutable['xcrun'] = [ - MockProcess(stdout: jsonEncode(devices)), - ]; - - expect(await xcode.findBestAvailableIphoneSimulator(), expectedDeviceId); - }); - - test('ignores non-iOS runtimes', () async { - // Note: This uses `dynamic` deliberately, and should not be updated to - // Object, in order to ensure that the code correctly handles this return - // type from JSON decoding. - final Map devices = { - 'runtimes': >[ - { - 'bundlePath': - '/Applications/Xcode_11_7.app/Contents/Developer/Platforms/WatchOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/watchOS.simruntime', - 'buildversion': '17T531', - 'runtimeRoot': - '/Applications/Xcode_11_7.app/Contents/Developer/Platforms/WatchOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/watchOS.simruntime/Contents/Resources/RuntimeRoot', - 'identifier': 'com.apple.CoreSimulator.SimRuntime.watchOS-6-2', - 'version': '6.2.1', - 'isAvailable': true, - 'name': 'watchOS 6.2' - } - ], - 'devices': { - 'com.apple.CoreSimulator.SimRuntime.watchOS-6-2': - >[ - { - 'dataPath': - '/Users/xxx/Library/Developer/CoreSimulator/Devices/1E76A0FD-38AC-4537-A989-EA639D7D012A/data', - 'logPath': - '/Users/xxx/Library/Logs/CoreSimulator/1E76A0FD-38AC-4537-A989-EA639D7D012A', - 'udid': '1E76A0FD-38AC-4537-A989-EA639D7D012A', - 'isAvailable': true, - 'deviceTypeIdentifier': - 'com.apple.CoreSimulator.SimDeviceType.Apple-Watch-38mm', - 'state': 'Shutdown', - 'name': 'Apple Watch' - } - ] - } - }; - - processRunner.mockProcessesForExecutable['xcrun'] = [ - MockProcess(stdout: jsonEncode(devices)), - ]; - - expect(await xcode.findBestAvailableIphoneSimulator(), null); - }); - - test('returns null if simctl fails', () async { - processRunner.mockProcessesForExecutable['xcrun'] = [ - MockProcess(exitCode: 1), - ]; - - expect(await xcode.findBestAvailableIphoneSimulator(), null); - }); - }); - - group('runXcodeBuild', () { - test('handles minimal arguments', () async { - final Directory directory = const LocalFileSystem().currentDirectory; - - final int exitCode = await xcode.runXcodeBuild( - directory, - workspace: 'A.xcworkspace', - scheme: 'AScheme', - ); - - expect(exitCode, 0); - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - 'xcrun', - const [ - 'xcodebuild', - 'build', - '-workspace', - 'A.xcworkspace', - '-scheme', - 'AScheme', - ], - directory.path), - ])); - }); - - test('handles all arguments', () async { - final Directory directory = const LocalFileSystem().currentDirectory; - - final int exitCode = await xcode.runXcodeBuild(directory, - actions: ['action1', 'action2'], - workspace: 'A.xcworkspace', - scheme: 'AScheme', - configuration: 'Debug', - extraFlags: ['-a', '-b', 'c=d']); - - expect(exitCode, 0); - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - 'xcrun', - const [ - 'xcodebuild', - 'action1', - 'action2', - '-workspace', - 'A.xcworkspace', - '-scheme', - 'AScheme', - '-configuration', - 'Debug', - '-a', - '-b', - 'c=d', - ], - directory.path), - ])); - }); - - test('returns error codes', () async { - processRunner.mockProcessesForExecutable['xcrun'] = [ - MockProcess(exitCode: 1), - ]; - final Directory directory = const LocalFileSystem().currentDirectory; - - final int exitCode = await xcode.runXcodeBuild( - directory, - workspace: 'A.xcworkspace', - scheme: 'AScheme', - ); - - expect(exitCode, 1); - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - 'xcrun', - const [ - 'xcodebuild', - 'build', - '-workspace', - 'A.xcworkspace', - '-scheme', - 'AScheme', - ], - directory.path), - ])); - }); - }); - - group('projectHasTarget', () { - test('returns true when present', () async { - const String stdout = ''' -{ - "project" : { - "configurations" : [ - "Debug", - "Release" - ], - "name" : "Runner", - "schemes" : [ - "Runner" - ], - "targets" : [ - "Runner", - "RunnerTests", - "RunnerUITests" - ] - } -}'''; - processRunner.mockProcessesForExecutable['xcrun'] = [ - MockProcess(stdout: stdout), - ]; - - final Directory project = - const LocalFileSystem().directory('/foo.xcodeproj'); - expect(await xcode.projectHasTarget(project, 'RunnerTests'), true); - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - 'xcrun', - [ - 'xcodebuild', - '-list', - '-json', - '-project', - project.path, - ], - null), - ])); - }); - - test('returns false when not present', () async { - const String stdout = ''' -{ - "project" : { - "configurations" : [ - "Debug", - "Release" - ], - "name" : "Runner", - "schemes" : [ - "Runner" - ], - "targets" : [ - "Runner", - "RunnerUITests" - ] - } -}'''; - processRunner.mockProcessesForExecutable['xcrun'] = [ - MockProcess(stdout: stdout), - ]; - - final Directory project = - const LocalFileSystem().directory('/foo.xcodeproj'); - expect(await xcode.projectHasTarget(project, 'RunnerTests'), false); - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - 'xcrun', - [ - 'xcodebuild', - '-list', - '-json', - '-project', - project.path, - ], - null), - ])); - }); - - test('returns null for unexpected output', () async { - processRunner.mockProcessesForExecutable['xcrun'] = [ - MockProcess(stdout: '{}'), - ]; - - final Directory project = - const LocalFileSystem().directory('/foo.xcodeproj'); - expect(await xcode.projectHasTarget(project, 'RunnerTests'), null); - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - 'xcrun', - [ - 'xcodebuild', - '-list', - '-json', - '-project', - project.path, - ], - null), - ])); - }); - - test('returns null for invalid output', () async { - processRunner.mockProcessesForExecutable['xcrun'] = [ - MockProcess(stdout: ':)'), - ]; - - final Directory project = - const LocalFileSystem().directory('/foo.xcodeproj'); - expect(await xcode.projectHasTarget(project, 'RunnerTests'), null); - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - 'xcrun', - [ - 'xcodebuild', - '-list', - '-json', - '-project', - project.path, - ], - null), - ])); - }); - - test('returns null for failure', () async { - processRunner.mockProcessesForExecutable['xcrun'] = [ - MockProcess(exitCode: 1), // xcodebuild -list - ]; - - final Directory project = - const LocalFileSystem().directory('/foo.xcodeproj'); - expect(await xcode.projectHasTarget(project, 'RunnerTests'), null); - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - 'xcrun', - [ - 'xcodebuild', - '-list', - '-json', - '-project', - project.path, - ], - null), - ])); - }); - }); -} diff --git a/script/tool/test/create_all_packages_app_command_test.dart b/script/tool/test/create_all_packages_app_command_test.dart deleted file mode 100644 index 54551cbc3712..000000000000 --- a/script/tool/test/create_all_packages_app_command_test.dart +++ /dev/null @@ -1,256 +0,0 @@ -// Copyright 2013 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. - -import 'dart:io' as io; - -import 'package:args/command_runner.dart'; -import 'package:file/file.dart'; -import 'package:file/local.dart'; -import 'package:flutter_plugin_tools/src/common/core.dart'; -import 'package:flutter_plugin_tools/src/create_all_packages_app_command.dart'; -import 'package:platform/platform.dart'; -import 'package:test/test.dart'; - -import 'mocks.dart'; -import 'util.dart'; - -void main() { - late CommandRunner runner; - late CreateAllPackagesAppCommand command; - late FileSystem fileSystem; - late Directory testRoot; - late Directory packagesDir; - late RecordingProcessRunner processRunner; - - setUp(() { - // Since the core of this command is a call to 'flutter create', the test - // has to use the real filesystem. Put everything possible in a unique - // temporary to minimize effect on the host system. - fileSystem = const LocalFileSystem(); - testRoot = fileSystem.systemTempDirectory.createTempSync(); - packagesDir = testRoot.childDirectory('packages'); - processRunner = RecordingProcessRunner(); - - command = CreateAllPackagesAppCommand( - packagesDir, - processRunner: processRunner, - pluginsRoot: testRoot, - ); - runner = CommandRunner( - 'create_all_test', 'Test for $CreateAllPackagesAppCommand'); - runner.addCommand(command); - }); - - tearDown(() { - testRoot.deleteSync(recursive: true); - }); - - group('non-macOS host', () { - setUp(() { - command = CreateAllPackagesAppCommand( - packagesDir, - processRunner: processRunner, - // Set isWindows or not based on the actual host, so that - // `flutterCommand` works, since these tests actually call 'flutter'. - // The important thing is that isMacOS always returns false. - platform: MockPlatform(isWindows: const LocalPlatform().isWindows), - pluginsRoot: testRoot, - ); - runner = CommandRunner( - 'create_all_test', 'Test for $CreateAllPackagesAppCommand'); - runner.addCommand(command); - }); - - test('pubspec includes all plugins', () async { - createFakePlugin('plugina', packagesDir); - createFakePlugin('pluginb', packagesDir); - createFakePlugin('pluginc', packagesDir); - - await runCapturingPrint(runner, ['create-all-packages-app']); - final List pubspec = command.app.pubspecFile.readAsLinesSync(); - - expect( - pubspec, - containsAll([ - contains(RegExp('path: .*/packages/plugina')), - contains(RegExp('path: .*/packages/pluginb')), - contains(RegExp('path: .*/packages/pluginc')), - ])); - }); - - test('pubspec has overrides for all plugins', () async { - createFakePlugin('plugina', packagesDir); - createFakePlugin('pluginb', packagesDir); - createFakePlugin('pluginc', packagesDir); - - await runCapturingPrint(runner, ['create-all-packages-app']); - final List pubspec = command.app.pubspecFile.readAsLinesSync(); - - expect( - pubspec, - containsAllInOrder([ - contains('dependency_overrides:'), - contains(RegExp('path: .*/packages/plugina')), - contains(RegExp('path: .*/packages/pluginb')), - contains(RegExp('path: .*/packages/pluginc')), - ])); - }); - - test('pubspec preserves existing Dart SDK version', () async { - const String baselineProjectName = 'baseline'; - final Directory baselineProjectDirectory = - testRoot.childDirectory(baselineProjectName); - io.Process.runSync( - getFlutterCommand(const LocalPlatform()), - [ - 'create', - '--template=app', - '--project-name=$baselineProjectName', - baselineProjectDirectory.path, - ], - ); - final Pubspec baselinePubspec = - RepositoryPackage(baselineProjectDirectory).parsePubspec(); - - createFakePlugin('plugina', packagesDir); - - await runCapturingPrint(runner, ['create-all-packages-app']); - final Pubspec generatedPubspec = command.app.parsePubspec(); - - const String dartSdkKey = 'sdk'; - expect(generatedPubspec.environment?[dartSdkKey], - baselinePubspec.environment?[dartSdkKey]); - }); - - test('macOS deployment target is modified in pbxproj', () async { - createFakePlugin('plugina', packagesDir); - - await runCapturingPrint(runner, ['create-all-packages-app']); - final List pbxproj = command.app - .platformDirectory(FlutterPlatform.macos) - .childDirectory('Runner.xcodeproj') - .childFile('project.pbxproj') - .readAsLinesSync(); - - expect( - pbxproj, - everyElement((String line) => - !line.contains('MACOSX_DEPLOYMENT_TARGET') || - line.contains('10.15'))); - }); - - test('calls flutter pub get', () async { - createFakePlugin('plugina', packagesDir); - - await runCapturingPrint(runner, ['create-all-packages-app']); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - getFlutterCommand(const LocalPlatform()), - const ['pub', 'get'], - testRoot.childDirectory('all_packages').path), - ])); - }, - // See comment about Windows in create_all_packages_app_command.dart - skip: io.Platform.isWindows); - - test('fails if flutter pub get fails', () async { - createFakePlugin('plugina', packagesDir); - - processRunner.mockProcessesForExecutable[ - getFlutterCommand(const LocalPlatform())] = [ - MockProcess(exitCode: 1) - ]; - Error? commandError; - final List output = await runCapturingPrint( - runner, ['create-all-packages-app'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains( - "Failed to generate native build files via 'flutter pub get'"), - ])); - }, - // See comment about Windows in create_all_packages_app_command.dart - skip: io.Platform.isWindows); - - test('handles --output-dir', () async { - createFakePlugin('plugina', packagesDir); - - final Directory customOutputDir = - fileSystem.systemTempDirectory.createTempSync(); - await runCapturingPrint(runner, [ - 'create-all-packages-app', - '--output-dir=${customOutputDir.path}' - ]); - - expect(command.app.path, - customOutputDir.childDirectory('all_packages').path); - }); - - test('logs exclusions', () async { - createFakePlugin('plugina', packagesDir); - createFakePlugin('pluginb', packagesDir); - createFakePlugin('pluginc', packagesDir); - - final List output = await runCapturingPrint(runner, - ['create-all-packages-app', '--exclude=pluginb,pluginc']); - - expect( - output, - containsAllInOrder([ - 'Exluding the following plugins from the combined build:', - ' pluginb', - ' pluginc', - ])); - }); - }); - - group('macOS host', () { - setUp(() { - command = CreateAllPackagesAppCommand( - packagesDir, - processRunner: processRunner, - platform: MockPlatform(isMacOS: true), - pluginsRoot: testRoot, - ); - runner = CommandRunner( - 'create_all_test', 'Test for $CreateAllPackagesAppCommand'); - runner.addCommand(command); - }); - - test('macOS deployment target is modified in Podfile', () async { - createFakePlugin('plugina', packagesDir); - - final File podfileFile = RepositoryPackage( - command.packagesDir.parent.childDirectory('all_packages')) - .platformDirectory(FlutterPlatform.macos) - .childFile('Podfile'); - podfileFile.createSync(recursive: true); - podfileFile.writeAsStringSync(""" -platform :osx, '10.11' -# some other line -"""); - - await runCapturingPrint(runner, ['create-all-packages-app']); - final List podfile = command.app - .platformDirectory(FlutterPlatform.macos) - .childFile('Podfile') - .readAsLinesSync(); - - expect( - podfile, - everyElement((String line) => - !line.contains('platform :osx') || line.contains("'10.15'"))); - }, - // Podfile is only generated (and thus only edited) on macOS. - skip: !io.Platform.isMacOS); - }); -} diff --git a/script/tool/test/custom_test_command_test.dart b/script/tool/test/custom_test_command_test.dart deleted file mode 100644 index 8b0c021b1255..000000000000 --- a/script/tool/test/custom_test_command_test.dart +++ /dev/null @@ -1,328 +0,0 @@ -// Copyright 2013 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. - -import 'dart:io' as io; - -import 'package:args/command_runner.dart'; -import 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:flutter_plugin_tools/src/common/core.dart'; -import 'package:flutter_plugin_tools/src/custom_test_command.dart'; -import 'package:test/test.dart'; - -import 'mocks.dart'; -import 'util.dart'; - -void main() { - late FileSystem fileSystem; - late MockPlatform mockPlatform; - late Directory packagesDir; - late RecordingProcessRunner processRunner; - late CommandRunner runner; - - group('posix', () { - setUp(() { - fileSystem = MemoryFileSystem(); - mockPlatform = MockPlatform(); - packagesDir = createPackagesDirectory(fileSystem: fileSystem); - processRunner = RecordingProcessRunner(); - final CustomTestCommand analyzeCommand = CustomTestCommand( - packagesDir, - processRunner: processRunner, - platform: mockPlatform, - ); - - runner = CommandRunner( - 'custom_test_command', 'Test for custom_test_command'); - runner.addCommand(analyzeCommand); - }); - - test('runs both new and legacy when both are present', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir, extraFiles: [ - 'tool/run_tests.dart', - 'run_tests.sh', - ]); - - final List output = - await runCapturingPrint(runner, ['custom-test']); - - expect( - processRunner.recordedCalls, - containsAll([ - ProcessCall(package.directory.childFile('run_tests.sh').path, - const [], package.path), - ProcessCall('dart', const ['run', 'tool/run_tests.dart'], - package.path), - ])); - - expect( - output, - containsAllInOrder([ - contains('Ran for 1 package(s)'), - ])); - }); - - test('runs when only new is present', () async { - final RepositoryPackage package = createFakePackage( - 'a_package', packagesDir, - extraFiles: ['tool/run_tests.dart']); - - final List output = - await runCapturingPrint(runner, ['custom-test']); - - expect( - processRunner.recordedCalls, - containsAll([ - ProcessCall('dart', const ['run', 'tool/run_tests.dart'], - package.path), - ])); - - expect( - output, - containsAllInOrder([ - contains('Ran for 1 package(s)'), - ])); - }); - - test('runs pub get before running Dart test script', () async { - final RepositoryPackage package = createFakePackage( - 'a_package', packagesDir, - extraFiles: ['tool/run_tests.dart']); - - await runCapturingPrint(runner, ['custom-test']); - - expect( - processRunner.recordedCalls, - containsAll([ - ProcessCall('dart', const ['pub', 'get'], package.path), - ProcessCall('dart', const ['run', 'tool/run_tests.dart'], - package.path), - ])); - }); - - test('runs when only legacy is present', () async { - final RepositoryPackage package = createFakePackage( - 'a_package', packagesDir, - extraFiles: ['run_tests.sh']); - - final List output = - await runCapturingPrint(runner, ['custom-test']); - - expect( - processRunner.recordedCalls, - containsAll([ - ProcessCall(package.directory.childFile('run_tests.sh').path, - const [], package.path), - ])); - - expect( - output, - containsAllInOrder([ - contains('Ran for 1 package(s)'), - ])); - }); - - test('skips when neither is present', () async { - createFakePackage('a_package', packagesDir); - - final List output = - await runCapturingPrint(runner, ['custom-test']); - - expect(processRunner.recordedCalls, isEmpty); - - expect( - output, - containsAllInOrder([ - contains('Skipped 1 package(s)'), - ])); - }); - - test('fails if new fails', () async { - createFakePackage('a_package', packagesDir, extraFiles: [ - 'tool/run_tests.dart', - 'run_tests.sh', - ]); - - processRunner.mockProcessesForExecutable['dart'] = [ - MockProcess(), // pub get - MockProcess(exitCode: 1), // test script - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['custom-test'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('The following packages had errors:'), - contains('a_package') - ])); - }); - - test('fails if pub get fails', () async { - createFakePackage('a_package', packagesDir, extraFiles: [ - 'tool/run_tests.dart', - 'run_tests.sh', - ]); - - processRunner.mockProcessesForExecutable['dart'] = [ - MockProcess(exitCode: 1), - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['custom-test'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('The following packages had errors:'), - contains('a_package:\n' - ' Unable to get script dependencies') - ])); - }); - - test('fails if legacy fails', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir, extraFiles: [ - 'tool/run_tests.dart', - 'run_tests.sh', - ]); - - processRunner.mockProcessesForExecutable[ - package.directory.childFile('run_tests.sh').path] = [ - MockProcess(exitCode: 1), - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['custom-test'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('The following packages had errors:'), - contains('a_package') - ])); - }); - }); - - group('Windows', () { - setUp(() { - fileSystem = MemoryFileSystem(style: FileSystemStyle.windows); - mockPlatform = MockPlatform(isWindows: true); - packagesDir = createPackagesDirectory(fileSystem: fileSystem); - processRunner = RecordingProcessRunner(); - final CustomTestCommand analyzeCommand = CustomTestCommand( - packagesDir, - processRunner: processRunner, - platform: mockPlatform, - ); - - runner = CommandRunner( - 'custom_test_command', 'Test for custom_test_command'); - runner.addCommand(analyzeCommand); - }); - - test('runs new and skips old when both are present', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir, extraFiles: [ - 'tool/run_tests.dart', - 'run_tests.sh', - ]); - - final List output = - await runCapturingPrint(runner, ['custom-test']); - - expect( - processRunner.recordedCalls, - containsAll([ - ProcessCall('dart', const ['run', 'tool/run_tests.dart'], - package.path), - ])); - - expect( - output, - containsAllInOrder([ - contains('Ran for 1 package(s)'), - ])); - }); - - test('runs when only new is present', () async { - final RepositoryPackage package = createFakePackage( - 'a_package', packagesDir, - extraFiles: ['tool/run_tests.dart']); - - final List output = - await runCapturingPrint(runner, ['custom-test']); - - expect( - processRunner.recordedCalls, - containsAll([ - ProcessCall('dart', const ['run', 'tool/run_tests.dart'], - package.path), - ])); - - expect( - output, - containsAllInOrder([ - contains('Ran for 1 package(s)'), - ])); - }); - - test('skips package when only legacy is present', () async { - createFakePackage('a_package', packagesDir, - extraFiles: ['run_tests.sh']); - - final List output = - await runCapturingPrint(runner, ['custom-test']); - - expect(processRunner.recordedCalls, isEmpty); - - expect( - output, - containsAllInOrder([ - contains('run_tests.sh is not supported on Windows'), - contains('Skipped 1 package(s)'), - ])); - }); - - test('fails if new fails', () async { - createFakePackage('a_package', packagesDir, extraFiles: [ - 'tool/run_tests.dart', - 'run_tests.sh', - ]); - - processRunner.mockProcessesForExecutable['dart'] = [ - MockProcess(), // pub get - MockProcess(exitCode: 1), // test script - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['custom-test'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('The following packages had errors:'), - contains('a_package') - ])); - }); - }); -} diff --git a/script/tool/test/dependabot_check_command_test.dart b/script/tool/test/dependabot_check_command_test.dart deleted file mode 100644 index 39dd8f4fcb92..000000000000 --- a/script/tool/test/dependabot_check_command_test.dart +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright 2013 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. - -import 'package:args/command_runner.dart'; -import 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:flutter_plugin_tools/src/common/core.dart'; -import 'package:flutter_plugin_tools/src/dependabot_check_command.dart'; -import 'package:mockito/mockito.dart'; -import 'package:test/test.dart'; - -import 'common/package_command_test.mocks.dart'; -import 'util.dart'; - -void main() { - late CommandRunner runner; - late FileSystem fileSystem; - late Directory root; - late Directory packagesDir; - - setUp(() { - fileSystem = MemoryFileSystem(); - root = fileSystem.currentDirectory; - packagesDir = root.childDirectory('packages'); - - final MockGitDir gitDir = MockGitDir(); - when(gitDir.path).thenReturn(root.path); - - final DependabotCheckCommand command = DependabotCheckCommand( - packagesDir, - gitDir: gitDir, - ); - runner = CommandRunner( - 'dependabot_test', 'Test for $DependabotCheckCommand'); - runner.addCommand(command); - }); - - void setDependabotCoverage({ - Iterable gradleDirs = const [], - }) { - final Iterable gradleEntries = - gradleDirs.map((String directory) => ''' - - package-ecosystem: "gradle" - directory: "/$directory" - schedule: - interval: "daily" -'''); - final File configFile = - root.childDirectory('.github').childFile('dependabot.yml'); - configFile.createSync(recursive: true); - configFile.writeAsStringSync(''' -version: 2 -updates: -${gradleEntries.join('\n')} -'''); - } - - test('skips with no supported ecosystems', () async { - setDependabotCoverage(); - createFakePackage('a_package', packagesDir); - - final List output = - await runCapturingPrint(runner, ['dependabot-check']); - - expect( - output, - containsAllInOrder([ - contains('SKIPPING: No supported package ecosystems'), - ])); - }); - - test('fails for app missing Gradle coverage', () async { - setDependabotCoverage(); - final RepositoryPackage package = - createFakePackage('a_package', packagesDir); - package.directory - .childDirectory('example') - .childDirectory('android') - .childDirectory('app') - .createSync(recursive: true); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['dependabot-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Missing Gradle coverage.'), - contains('a_package/example:\n' - ' Missing Gradle coverage') - ])); - }); - - test('fails for plugin missing Gradle coverage', () async { - setDependabotCoverage(); - final RepositoryPackage plugin = createFakePlugin('a_plugin', packagesDir); - plugin.directory.childDirectory('android').createSync(recursive: true); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['dependabot-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Missing Gradle coverage.'), - contains('a_plugin:\n' - ' Missing Gradle coverage') - ])); - }); - - test('passes for correct Gradle coverage', () async { - setDependabotCoverage(gradleDirs: [ - 'packages/a_plugin/android', - 'packages/a_plugin/example/android/app', - ]); - final RepositoryPackage plugin = createFakePlugin('a_plugin', packagesDir); - // Test the plugin. - plugin.directory.childDirectory('android').createSync(recursive: true); - // And its example app. - plugin.directory - .childDirectory('example') - .childDirectory('android') - .childDirectory('app') - .createSync(recursive: true); - - final List output = - await runCapturingPrint(runner, ['dependabot-check']); - - expect(output, - containsAllInOrder([contains('Ran for 2 package(s)')])); - }); -} diff --git a/script/tool/test/drive_examples_command_test.dart b/script/tool/test/drive_examples_command_test.dart deleted file mode 100644 index 0b6082098ae8..000000000000 --- a/script/tool/test/drive_examples_command_test.dart +++ /dev/null @@ -1,1257 +0,0 @@ -// Copyright 2013 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. - -import 'dart:convert'; -import 'dart:io' as io; - -import 'package:args/command_runner.dart'; -import 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:flutter_plugin_tools/src/common/core.dart'; -import 'package:flutter_plugin_tools/src/common/plugin_utils.dart'; -import 'package:flutter_plugin_tools/src/drive_examples_command.dart'; -import 'package:platform/platform.dart'; -import 'package:test/test.dart'; - -import 'mocks.dart'; -import 'util.dart'; - -const String _fakeIOSDevice = '67d5c3d1-8bdf-46ad-8f6b-b00e2a972dda'; -const String _fakeAndroidDevice = 'emulator-1234'; - -void main() { - group('test drive_example_command', () { - late FileSystem fileSystem; - late Platform mockPlatform; - late Directory packagesDir; - late CommandRunner runner; - late RecordingProcessRunner processRunner; - - setUp(() { - fileSystem = MemoryFileSystem(); - mockPlatform = MockPlatform(); - packagesDir = createPackagesDirectory(fileSystem: fileSystem); - processRunner = RecordingProcessRunner(); - final DriveExamplesCommand command = DriveExamplesCommand(packagesDir, - processRunner: processRunner, platform: mockPlatform); - - runner = CommandRunner( - 'drive_examples_command', 'Test for drive_example_command'); - runner.addCommand(command); - }); - - void setMockFlutterDevicesOutput({ - bool hasIOSDevice = true, - bool hasAndroidDevice = true, - bool includeBanner = false, - }) { - const String updateBanner = ''' -╔════════════════════════════════════════════════════════════════════════════╗ -║ A new version of Flutter is available! ║ -║ ║ -║ To update to the latest version, run "flutter upgrade". ║ -╚════════════════════════════════════════════════════════════════════════════╝ -'''; - final List devices = [ - if (hasIOSDevice) '{"id": "$_fakeIOSDevice", "targetPlatform": "ios"}', - if (hasAndroidDevice) - '{"id": "$_fakeAndroidDevice", "targetPlatform": "android-x86"}', - ]; - final String output = - '''${includeBanner ? updateBanner : ''}[${devices.join(',')}]'''; - - final MockProcess mockDevicesProcess = - MockProcess(stdout: output, stdoutEncoding: utf8); - processRunner - .mockProcessesForExecutable[getFlutterCommand(mockPlatform)] = - [mockDevicesProcess]; - } - - test('fails if no platforms are provided', () async { - setMockFlutterDevicesOutput(); - Error? commandError; - final List output = await runCapturingPrint( - runner, ['drive-examples'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Exactly one of'), - ]), - ); - }); - - test('fails if multiple platforms are provided', () async { - setMockFlutterDevicesOutput(); - Error? commandError; - final List output = await runCapturingPrint( - runner, ['drive-examples', '--ios', '--macos'], - errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Exactly one of'), - ]), - ); - }); - - test('fails for iOS if no iOS devices are present', () async { - setMockFlutterDevicesOutput(hasIOSDevice: false); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['drive-examples', '--ios'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('No iOS devices'), - ]), - ); - }); - - test('handles flutter tool banners when checking devices', () async { - createFakePlugin( - 'plugin', - packagesDir, - extraFiles: [ - 'example/test_driver/integration_test.dart', - 'example/integration_test/foo_test.dart', - 'example/ios/ios.m', - ], - platformSupport: { - platformIOS: const PlatformDetails(PlatformSupport.inline), - }, - ); - - setMockFlutterDevicesOutput(includeBanner: true); - final List output = - await runCapturingPrint(runner, ['drive-examples', '--ios']); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('No issues found!'), - ]), - ); - }); - - test('fails for iOS if getting devices fails', () async { - // Simulate failure from `flutter devices`. - processRunner - .mockProcessesForExecutable[getFlutterCommand(mockPlatform)] = - [MockProcess(exitCode: 1)]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['drive-examples', '--ios'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('No iOS devices'), - ]), - ); - }); - - test('fails for Android if no Android devices are present', () async { - setMockFlutterDevicesOutput(hasAndroidDevice: false); - Error? commandError; - final List output = await runCapturingPrint( - runner, ['drive-examples', '--android'], - errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('No Android devices'), - ]), - ); - }); - - test('driving under folder "test_driver"', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', - packagesDir, - extraFiles: [ - 'example/test_driver/plugin_test.dart', - 'example/test_driver/plugin.dart', - 'example/android/android.java', - 'example/ios/ios.m', - ], - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline), - platformIOS: const PlatformDetails(PlatformSupport.inline), - }, - ); - - final Directory pluginExampleDirectory = getExampleDir(plugin); - - setMockFlutterDevicesOutput(); - final List output = - await runCapturingPrint(runner, ['drive-examples', '--ios']); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('No issues found!'), - ]), - ); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall(getFlutterCommand(mockPlatform), - const ['devices', '--machine'], null), - ProcessCall( - getFlutterCommand(mockPlatform), - const [ - 'drive', - '-d', - _fakeIOSDevice, - '--driver', - 'test_driver/plugin_test.dart', - '--target', - 'test_driver/plugin.dart' - ], - pluginExampleDirectory.path), - ])); - }); - - test('driving under folder "test_driver" when test files are missing"', - () async { - setMockFlutterDevicesOutput(); - createFakePlugin( - 'plugin', - packagesDir, - extraFiles: [ - 'example/test_driver/plugin_test.dart', - 'example/android/android.java', - 'example/ios/ios.m', - ], - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline), - platformIOS: const PlatformDetails(PlatformSupport.inline), - }, - ); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['drive-examples', '--android'], - errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('No driver tests were run (1 example(s) found).'), - contains('No test files for example/test_driver/plugin_test.dart'), - ]), - ); - }); - - test('a plugin without any integration test files is reported as an error', - () async { - setMockFlutterDevicesOutput(); - createFakePlugin( - 'plugin', - packagesDir, - extraFiles: [ - 'example/lib/main.dart', - 'example/android/android.java', - 'example/ios/ios.m', - ], - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline), - platformIOS: const PlatformDetails(PlatformSupport.inline), - }, - ); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['drive-examples', '--android'], - errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('No driver tests were run (1 example(s) found).'), - contains('No tests ran'), - ]), - ); - }); - - test('integration tests using test(...) fail validation', () async { - setMockFlutterDevicesOutput(); - final RepositoryPackage package = createFakePlugin( - 'plugin', - packagesDir, - extraFiles: [ - 'example/test_driver/integration_test.dart', - 'example/integration_test/foo_test.dart', - 'example/android/android.java', - ], - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline), - platformIOS: const PlatformDetails(PlatformSupport.inline), - }, - ); - package.directory - .childDirectory('example') - .childDirectory('integration_test') - .childFile('foo_test.dart') - .writeAsStringSync(''' - test('this is the wrong kind of test!'), () { - ... - } -'''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['drive-examples', '--android'], - errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('foo_test.dart failed validation'), - ]), - ); - }); - - test( - 'driving under folder "test_driver" when targets are under "integration_test"', - () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', - packagesDir, - extraFiles: [ - 'example/test_driver/integration_test.dart', - 'example/integration_test/bar_test.dart', - 'example/integration_test/foo_test.dart', - 'example/integration_test/ignore_me.dart', - 'example/android/android.java', - 'example/ios/ios.m', - ], - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline), - platformIOS: const PlatformDetails(PlatformSupport.inline), - }, - ); - - final Directory pluginExampleDirectory = getExampleDir(plugin); - - setMockFlutterDevicesOutput(); - final List output = - await runCapturingPrint(runner, ['drive-examples', '--ios']); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('No issues found!'), - ]), - ); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall(getFlutterCommand(mockPlatform), - const ['devices', '--machine'], null), - ProcessCall( - getFlutterCommand(mockPlatform), - const [ - 'drive', - '-d', - _fakeIOSDevice, - '--driver', - 'test_driver/integration_test.dart', - '--target', - 'integration_test/bar_test.dart', - ], - pluginExampleDirectory.path), - ProcessCall( - getFlutterCommand(mockPlatform), - const [ - 'drive', - '-d', - _fakeIOSDevice, - '--driver', - 'test_driver/integration_test.dart', - '--target', - 'integration_test/foo_test.dart', - ], - pluginExampleDirectory.path), - ])); - }); - - test('driving when plugin does not support Linux is a no-op', () async { - createFakePlugin('plugin', packagesDir, extraFiles: [ - 'example/test_driver/plugin_test.dart', - 'example/test_driver/plugin.dart', - ]); - - final List output = await runCapturingPrint(runner, [ - 'drive-examples', - '--linux', - ]); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('Skipping unsupported platform linux...'), - contains('No issues found!'), - ]), - ); - - // Output should be empty since running drive-examples --linux on a non-Linux - // plugin is a no-op. - expect(processRunner.recordedCalls, []); - }); - - test('driving on a Linux plugin', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', - packagesDir, - extraFiles: [ - 'example/test_driver/plugin_test.dart', - 'example/test_driver/plugin.dart', - 'example/linux/linux.cc', - ], - platformSupport: { - platformLinux: const PlatformDetails(PlatformSupport.inline), - }, - ); - - final Directory pluginExampleDirectory = getExampleDir(plugin); - - final List output = await runCapturingPrint(runner, [ - 'drive-examples', - '--linux', - ]); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('No issues found!'), - ]), - ); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - getFlutterCommand(mockPlatform), - const [ - 'drive', - '-d', - 'linux', - '--driver', - 'test_driver/plugin_test.dart', - '--target', - 'test_driver/plugin.dart' - ], - pluginExampleDirectory.path), - ])); - }); - - test('driving when plugin does not suppport macOS is a no-op', () async { - createFakePlugin('plugin', packagesDir, extraFiles: [ - 'example/test_driver/plugin_test.dart', - 'example/test_driver/plugin.dart', - ]); - - final List output = await runCapturingPrint(runner, [ - 'drive-examples', - '--macos', - ]); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('Skipping unsupported platform macos...'), - contains('No issues found!'), - ]), - ); - - // Output should be empty since running drive-examples --macos with no macos - // implementation is a no-op. - expect(processRunner.recordedCalls, []); - }); - - test('driving on a macOS plugin', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', - packagesDir, - extraFiles: [ - 'example/test_driver/plugin_test.dart', - 'example/test_driver/plugin.dart', - 'example/macos/macos.swift', - ], - platformSupport: { - platformMacOS: const PlatformDetails(PlatformSupport.inline), - }, - ); - - final Directory pluginExampleDirectory = getExampleDir(plugin); - - final List output = await runCapturingPrint(runner, [ - 'drive-examples', - '--macos', - ]); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('No issues found!'), - ]), - ); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - getFlutterCommand(mockPlatform), - const [ - 'drive', - '-d', - 'macos', - '--driver', - 'test_driver/plugin_test.dart', - '--target', - 'test_driver/plugin.dart' - ], - pluginExampleDirectory.path), - ])); - }); - - test('driving when plugin does not suppport web is a no-op', () async { - createFakePlugin('plugin', packagesDir, extraFiles: [ - 'example/test_driver/plugin_test.dart', - 'example/test_driver/plugin.dart', - ]); - - final List output = await runCapturingPrint(runner, [ - 'drive-examples', - '--web', - ]); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('No issues found!'), - ]), - ); - - // Output should be empty since running drive-examples --web on a non-web - // plugin is a no-op. - expect(processRunner.recordedCalls, []); - }); - - test('driving a web plugin', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', - packagesDir, - extraFiles: [ - 'example/test_driver/plugin_test.dart', - 'example/test_driver/plugin.dart', - 'example/web/index.html', - ], - platformSupport: { - platformWeb: const PlatformDetails(PlatformSupport.inline), - }, - ); - - final Directory pluginExampleDirectory = getExampleDir(plugin); - - final List output = await runCapturingPrint(runner, [ - 'drive-examples', - '--web', - ]); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('No issues found!'), - ]), - ); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - getFlutterCommand(mockPlatform), - const [ - 'drive', - '-d', - 'web-server', - '--web-port=7357', - '--browser-name=chrome', - '--driver', - 'test_driver/plugin_test.dart', - '--target', - 'test_driver/plugin.dart' - ], - pluginExampleDirectory.path), - ])); - }); - - test('driving a web plugin with CHROME_EXECUTABLE', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', - packagesDir, - extraFiles: [ - 'example/test_driver/plugin_test.dart', - 'example/test_driver/plugin.dart', - 'example/web/index.html', - ], - platformSupport: { - platformWeb: const PlatformDetails(PlatformSupport.inline), - }, - ); - - final Directory pluginExampleDirectory = getExampleDir(plugin); - - mockPlatform.environment['CHROME_EXECUTABLE'] = '/path/to/chrome'; - - final List output = await runCapturingPrint(runner, [ - 'drive-examples', - '--web', - ]); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('No issues found!'), - ]), - ); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - getFlutterCommand(mockPlatform), - const [ - 'drive', - '-d', - 'web-server', - '--web-port=7357', - '--browser-name=chrome', - '--chrome-binary=/path/to/chrome', - '--driver', - 'test_driver/plugin_test.dart', - '--target', - 'test_driver/plugin.dart' - ], - pluginExampleDirectory.path), - ])); - }); - - test('driving when plugin does not suppport Windows is a no-op', () async { - createFakePlugin('plugin', packagesDir, extraFiles: [ - 'example/test_driver/plugin_test.dart', - 'example/test_driver/plugin.dart', - ]); - - final List output = await runCapturingPrint(runner, [ - 'drive-examples', - '--windows', - ]); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('Skipping unsupported platform windows...'), - contains('No issues found!'), - ]), - ); - - // Output should be empty since running drive-examples --windows on a - // non-Windows plugin is a no-op. - expect(processRunner.recordedCalls, []); - }); - - test('driving on a Windows plugin', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', - packagesDir, - extraFiles: [ - 'example/test_driver/plugin_test.dart', - 'example/test_driver/plugin.dart', - 'example/windows/windows.cpp', - ], - platformSupport: { - platformWindows: const PlatformDetails(PlatformSupport.inline), - }, - ); - - final Directory pluginExampleDirectory = getExampleDir(plugin); - - final List output = await runCapturingPrint(runner, [ - 'drive-examples', - '--windows', - ]); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('No issues found!'), - ]), - ); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - getFlutterCommand(mockPlatform), - const [ - 'drive', - '-d', - 'windows', - '--driver', - 'test_driver/plugin_test.dart', - '--target', - 'test_driver/plugin.dart' - ], - pluginExampleDirectory.path), - ])); - }); - - test('driving on an Android plugin', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', - packagesDir, - extraFiles: [ - 'example/test_driver/plugin_test.dart', - 'example/test_driver/plugin.dart', - 'example/android/android.java', - ], - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline), - }, - ); - - final Directory pluginExampleDirectory = getExampleDir(plugin); - - setMockFlutterDevicesOutput(); - final List output = await runCapturingPrint(runner, [ - 'drive-examples', - '--android', - ]); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('No issues found!'), - ]), - ); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall(getFlutterCommand(mockPlatform), - const ['devices', '--machine'], null), - ProcessCall( - getFlutterCommand(mockPlatform), - const [ - 'drive', - '-d', - _fakeAndroidDevice, - '--driver', - 'test_driver/plugin_test.dart', - '--target', - 'test_driver/plugin.dart' - ], - pluginExampleDirectory.path), - ])); - }); - - test('driving when plugin does not support Android is no-op', () async { - createFakePlugin( - 'plugin', - packagesDir, - extraFiles: [ - 'example/test_driver/plugin_test.dart', - 'example/test_driver/plugin.dart', - ], - platformSupport: { - platformMacOS: const PlatformDetails(PlatformSupport.inline), - }, - ); - - setMockFlutterDevicesOutput(); - final List output = await runCapturingPrint( - runner, ['drive-examples', '--android']); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('Skipping unsupported platform android...'), - contains('No issues found!'), - ]), - ); - - // Output should be empty other than the device query. - expect(processRunner.recordedCalls, [ - ProcessCall(getFlutterCommand(mockPlatform), - const ['devices', '--machine'], null), - ]); - }); - - test('driving when plugin does not support iOS is no-op', () async { - createFakePlugin( - 'plugin', - packagesDir, - extraFiles: [ - 'example/test_driver/plugin_test.dart', - 'example/test_driver/plugin.dart', - ], - platformSupport: { - platformMacOS: const PlatformDetails(PlatformSupport.inline), - }, - ); - - setMockFlutterDevicesOutput(); - final List output = - await runCapturingPrint(runner, ['drive-examples', '--ios']); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('Skipping unsupported platform ios...'), - contains('No issues found!'), - ]), - ); - - // Output should be empty other than the device query. - expect(processRunner.recordedCalls, [ - ProcessCall(getFlutterCommand(mockPlatform), - const ['devices', '--machine'], null), - ]); - }); - - test('platform interface plugins are silently skipped', () async { - createFakePlugin('aplugin_platform_interface', packagesDir, - examples: []); - - setMockFlutterDevicesOutput(); - final List output = await runCapturingPrint( - runner, ['drive-examples', '--macos']); - - expect( - output, - containsAllInOrder([ - contains('Running for aplugin_platform_interface'), - contains( - 'SKIPPING: Platform interfaces are not expected to have integration tests.'), - contains('No issues found!'), - ]), - ); - - // Output should be empty since it's skipped. - expect(processRunner.recordedCalls, []); - }); - - test('enable-experiment flag', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', - packagesDir, - extraFiles: [ - 'example/test_driver/plugin_test.dart', - 'example/test_driver/plugin.dart', - 'example/android/android.java', - 'example/ios/ios.m', - ], - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline), - platformIOS: const PlatformDetails(PlatformSupport.inline), - }, - ); - - final Directory pluginExampleDirectory = getExampleDir(plugin); - - setMockFlutterDevicesOutput(); - await runCapturingPrint(runner, [ - 'drive-examples', - '--ios', - '--enable-experiment=exp1', - ]); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall(getFlutterCommand(mockPlatform), - const ['devices', '--machine'], null), - ProcessCall( - getFlutterCommand(mockPlatform), - const [ - 'drive', - '-d', - _fakeIOSDevice, - '--enable-experiment=exp1', - '--driver', - 'test_driver/plugin_test.dart', - '--target', - 'test_driver/plugin.dart' - ], - pluginExampleDirectory.path), - ])); - }); - - test('fails when no example is present', () async { - createFakePlugin( - 'plugin', - packagesDir, - examples: [], - platformSupport: { - platformWeb: const PlatformDetails(PlatformSupport.inline), - }, - ); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['drive-examples', '--web'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('No driver tests were run (0 example(s) found).'), - contains('The following packages had errors:'), - contains(' plugin:\n' - ' No tests ran (use --exclude if this is intentional)'), - ]), - ); - }); - - test('fails when no driver is present', () async { - createFakePlugin( - 'plugin', - packagesDir, - extraFiles: [ - 'example/integration_test/bar_test.dart', - 'example/integration_test/foo_test.dart', - 'example/web/index.html', - ], - platformSupport: { - platformWeb: const PlatformDetails(PlatformSupport.inline), - }, - ); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['drive-examples', '--web'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('No driver tests found for plugin/example'), - contains('No driver tests were run (1 example(s) found).'), - contains('The following packages had errors:'), - contains(' plugin:\n' - ' No tests ran (use --exclude if this is intentional)'), - ]), - ); - }); - - test('fails when no integration tests are present', () async { - createFakePlugin( - 'plugin', - packagesDir, - extraFiles: [ - 'example/test_driver/integration_test.dart', - 'example/web/index.html', - ], - platformSupport: { - platformWeb: const PlatformDetails(PlatformSupport.inline), - }, - ); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['drive-examples', '--web'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('Found example/test_driver/integration_test.dart, but no ' - 'integration_test/*_test.dart files.'), - contains('No driver tests were run (1 example(s) found).'), - contains('The following packages had errors:'), - contains(' plugin:\n' - ' No test files for example/test_driver/integration_test.dart\n' - ' No tests ran (use --exclude if this is intentional)'), - ]), - ); - }); - - test('reports test failures', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', - packagesDir, - extraFiles: [ - 'example/test_driver/integration_test.dart', - 'example/integration_test/bar_test.dart', - 'example/integration_test/foo_test.dart', - 'example/macos/macos.swift', - ], - platformSupport: { - platformMacOS: const PlatformDetails(PlatformSupport.inline), - }, - ); - - // Simulate failure from `flutter drive`. - processRunner - .mockProcessesForExecutable[getFlutterCommand(mockPlatform)] = - [ - // No mock for 'devices', since it's running for macOS. - MockProcess(exitCode: 1), // 'drive' #1 - MockProcess(exitCode: 1), // 'drive' #2 - ]; - - Error? commandError; - final List output = - await runCapturingPrint(runner, ['drive-examples', '--macos'], - errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('The following packages had errors:'), - contains(' plugin:\n' - ' example/integration_test/bar_test.dart\n' - ' example/integration_test/foo_test.dart'), - ]), - ); - - final Directory pluginExampleDirectory = getExampleDir(plugin); - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - getFlutterCommand(mockPlatform), - const [ - 'drive', - '-d', - 'macos', - '--driver', - 'test_driver/integration_test.dart', - '--target', - 'integration_test/bar_test.dart', - ], - pluginExampleDirectory.path), - ProcessCall( - getFlutterCommand(mockPlatform), - const [ - 'drive', - '-d', - 'macos', - '--driver', - 'test_driver/integration_test.dart', - '--target', - 'integration_test/foo_test.dart', - ], - pluginExampleDirectory.path), - ])); - }); - - group('packages', () { - test('can be driven', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir, extraFiles: [ - 'example/integration_test/foo_test.dart', - 'example/test_driver/integration_test.dart', - 'example/web/index.html', - ]); - final Directory exampleDirectory = getExampleDir(package); - - final List output = await runCapturingPrint(runner, [ - 'drive-examples', - '--web', - ]); - - expect( - output, - containsAllInOrder([ - contains('Running for a_package'), - contains('No issues found!'), - ]), - ); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - getFlutterCommand(mockPlatform), - const [ - 'drive', - '-d', - 'web-server', - '--web-port=7357', - '--browser-name=chrome', - '--driver', - 'test_driver/integration_test.dart', - '--target', - 'integration_test/foo_test.dart' - ], - exampleDirectory.path), - ])); - }); - - test('are skipped when example does not support platform', () async { - createFakePackage('a_package', packagesDir, - isFlutter: true, - extraFiles: [ - 'example/integration_test/foo_test.dart', - 'example/test_driver/integration_test.dart', - ]); - - final List output = await runCapturingPrint(runner, [ - 'drive-examples', - '--web', - ]); - - expect( - output, - containsAllInOrder([ - contains('Running for a_package'), - contains('Skipping a_package/example; does not support any ' - 'requested platforms'), - contains('SKIPPING: No example supports requested platform(s).'), - ]), - ); - - expect(processRunner.recordedCalls.isEmpty, true); - }); - - test('drive only supported examples if there is more than one', () async { - final RepositoryPackage package = createFakePackage( - 'a_package', packagesDir, - isFlutter: true, - examples: [ - 'with_web', - 'without_web' - ], - extraFiles: [ - 'example/with_web/integration_test/foo_test.dart', - 'example/with_web/test_driver/integration_test.dart', - 'example/with_web/web/index.html', - 'example/without_web/integration_test/foo_test.dart', - 'example/without_web/test_driver/integration_test.dart', - ]); - final Directory supportedExampleDirectory = - getExampleDir(package).childDirectory('with_web'); - - final List output = await runCapturingPrint(runner, [ - 'drive-examples', - '--web', - ]); - - expect( - output, - containsAllInOrder([ - contains('Running for a_package'), - contains( - 'Skipping a_package/example/without_web; does not support any requested platforms.'), - contains('No issues found!'), - ]), - ); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - getFlutterCommand(mockPlatform), - const [ - 'drive', - '-d', - 'web-server', - '--web-port=7357', - '--browser-name=chrome', - '--driver', - 'test_driver/integration_test.dart', - '--target', - 'integration_test/foo_test.dart' - ], - supportedExampleDirectory.path), - ])); - }); - - test('are skipped when there is no integration testing', () async { - createFakePackage('a_package', packagesDir, - isFlutter: true, extraFiles: ['example/web/index.html']); - - final List output = await runCapturingPrint(runner, [ - 'drive-examples', - '--web', - ]); - - expect( - output, - containsAllInOrder([ - contains('Running for a_package'), - contains('SKIPPING: No example is configured for driver tests.'), - ]), - ); - - expect(processRunner.recordedCalls.isEmpty, true); - }); - }); - }); -} diff --git a/script/tool/test/federation_safety_check_command_test.dart b/script/tool/test/federation_safety_check_command_test.dart deleted file mode 100644 index 6b6b1a514531..000000000000 --- a/script/tool/test/federation_safety_check_command_test.dart +++ /dev/null @@ -1,411 +0,0 @@ -// Copyright 2013 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. - -import 'dart:io' as io; - -import 'package:args/command_runner.dart'; -import 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:flutter_plugin_tools/src/common/core.dart'; -import 'package:flutter_plugin_tools/src/federation_safety_check_command.dart'; -import 'package:mockito/mockito.dart'; -import 'package:test/test.dart'; - -import 'common/package_command_test.mocks.dart'; -import 'mocks.dart'; -import 'util.dart'; - -void main() { - FileSystem fileSystem; - late MockPlatform mockPlatform; - late Directory packagesDir; - late CommandRunner runner; - late RecordingProcessRunner processRunner; - - setUp(() { - fileSystem = MemoryFileSystem(); - mockPlatform = MockPlatform(); - packagesDir = createPackagesDirectory(fileSystem: fileSystem); - - final MockGitDir gitDir = MockGitDir(); - when(gitDir.path).thenReturn(packagesDir.parent.path); - when(gitDir.runCommand(any, throwOnError: anyNamed('throwOnError'))) - .thenAnswer((Invocation invocation) { - final List arguments = - invocation.positionalArguments[0]! as List; - // Route git calls through the process runner, to make mock output - // consistent with other processes. Attach the first argument to the - // command to make targeting the mock results easier. - final String gitCommand = arguments.removeAt(0); - return processRunner.run('git-$gitCommand', arguments); - }); - - processRunner = RecordingProcessRunner(); - final FederationSafetyCheckCommand command = FederationSafetyCheckCommand( - packagesDir, - processRunner: processRunner, - platform: mockPlatform, - gitDir: gitDir); - - runner = CommandRunner('federation_safety_check_command', - 'Test for $FederationSafetyCheckCommand'); - runner.addCommand(command); - }); - - test('skips non-plugin packages', () async { - final RepositoryPackage package = createFakePackage('foo', packagesDir); - - final String changedFileOutput = [ - package.libDirectory.childFile('foo.dart'), - ].map((File file) => file.path).join('\n'); - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: changedFileOutput), - ]; - - final List output = - await runCapturingPrint(runner, ['federation-safety-check']); - - expect( - output, - containsAllInOrder([ - contains('Running for foo...'), - contains('Not a plugin'), - contains('Skipped 1 package(s)'), - ]), - ); - }); - - test('skips unfederated plugins', () async { - final RepositoryPackage package = createFakePlugin('foo', packagesDir); - - final String changedFileOutput = [ - package.libDirectory.childFile('foo.dart'), - ].map((File file) => file.path).join('\n'); - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: changedFileOutput), - ]; - - final List output = - await runCapturingPrint(runner, ['federation-safety-check']); - - expect( - output, - containsAllInOrder([ - contains('Running for foo...'), - contains('Not a federated plugin'), - contains('Skipped 1 package(s)'), - ]), - ); - }); - - test('skips interface packages', () async { - final Directory pluginGroupDir = packagesDir.childDirectory('foo'); - final RepositoryPackage platformInterface = - createFakePlugin('foo_platform_interface', pluginGroupDir); - - final String changedFileOutput = [ - platformInterface.libDirectory.childFile('foo.dart'), - ].map((File file) => file.path).join('\n'); - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: changedFileOutput), - ]; - - final List output = - await runCapturingPrint(runner, ['federation-safety-check']); - - expect( - output, - containsAllInOrder([ - contains('Running for foo_platform_interface...'), - contains('Platform interface changes are not validated.'), - contains('Skipped 1 package(s)'), - ]), - ); - }); - - test('allows changes to just an interface package', () async { - final Directory pluginGroupDir = packagesDir.childDirectory('foo'); - final RepositoryPackage platformInterface = - createFakePlugin('foo_platform_interface', pluginGroupDir); - createFakePlugin('foo', pluginGroupDir); - createFakePlugin('foo_ios', pluginGroupDir); - createFakePlugin('foo_android', pluginGroupDir); - - final String changedFileOutput = [ - platformInterface.libDirectory.childFile('foo.dart'), - platformInterface.pubspecFile, - ].map((File file) => file.path).join('\n'); - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: changedFileOutput), - ]; - - final List output = - await runCapturingPrint(runner, ['federation-safety-check']); - - expect( - output, - containsAllInOrder([ - contains('Running for foo/foo...'), - contains('No Dart changes.'), - contains('Running for foo_android...'), - contains('No Dart changes.'), - contains('Running for foo_ios...'), - contains('No Dart changes.'), - contains('Running for foo_platform_interface...'), - contains('Ran for 3 package(s)'), - contains('Skipped 1 package(s)'), - ]), - ); - expect( - output, - isNot(contains([ - contains('No published changes for foo_platform_interface'), - ])), - ); - }); - - test('allows changes to multiple non-interface packages', () async { - final Directory pluginGroupDir = packagesDir.childDirectory('foo'); - final RepositoryPackage appFacing = createFakePlugin('foo', pluginGroupDir); - final RepositoryPackage implementation = - createFakePlugin('foo_bar', pluginGroupDir); - createFakePlugin('foo_platform_interface', pluginGroupDir); - - final String changedFileOutput = [ - appFacing.libDirectory.childFile('foo.dart'), - implementation.libDirectory.childFile('foo.dart'), - ].map((File file) => file.path).join('\n'); - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: changedFileOutput), - ]; - - final List output = - await runCapturingPrint(runner, ['federation-safety-check']); - - expect( - output, - containsAllInOrder([ - contains('Running for foo/foo...'), - contains('No published changes for foo_platform_interface.'), - contains('Running for foo_bar...'), - contains('No published changes for foo_platform_interface.'), - ]), - ); - }); - - test( - 'fails on changes to interface and non-interface packages in the same plugin', - () async { - final Directory pluginGroupDir = packagesDir.childDirectory('foo'); - final RepositoryPackage appFacing = createFakePlugin('foo', pluginGroupDir); - final RepositoryPackage implementation = - createFakePlugin('foo_bar', pluginGroupDir); - final RepositoryPackage platformInterface = - createFakePlugin('foo_platform_interface', pluginGroupDir); - - final String changedFileOutput = [ - appFacing.libDirectory.childFile('foo.dart'), - implementation.libDirectory.childFile('foo.dart'), - platformInterface.pubspecFile, - platformInterface.libDirectory.childFile('foo.dart'), - ].map((File file) => file.path).join('\n'); - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: changedFileOutput), - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['federation-safety-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Running for foo/foo...'), - contains('Dart changes are not allowed to other packages in foo in the ' - 'same PR as changes to public Dart code in foo_platform_interface, ' - 'as this can cause accidental breaking changes to be missed by ' - 'automated checks. Please split the changes to these two packages ' - 'into separate PRs.'), - contains('Running for foo_bar...'), - contains('Dart changes are not allowed to other packages in foo'), - contains('The following packages had errors:'), - contains('foo/foo:\n' - ' foo_platform_interface changed.'), - contains('foo_bar:\n' - ' foo_platform_interface changed.'), - ]), - ); - }); - - test('ignores test-only changes to interface packages', () async { - final Directory pluginGroupDir = packagesDir.childDirectory('foo'); - final RepositoryPackage appFacing = createFakePlugin('foo', pluginGroupDir); - final RepositoryPackage implementation = - createFakePlugin('foo_bar', pluginGroupDir); - final RepositoryPackage platformInterface = - createFakePlugin('foo_platform_interface', pluginGroupDir); - - final String changedFileOutput = [ - appFacing.libDirectory.childFile('foo.dart'), - implementation.libDirectory.childFile('foo.dart'), - platformInterface.pubspecFile, - platformInterface.testDirectory.childFile('foo.dart'), - ].map((File file) => file.path).join('\n'); - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: changedFileOutput), - ]; - - final List output = - await runCapturingPrint(runner, ['federation-safety-check']); - - expect( - output, - containsAllInOrder([ - contains('Running for foo/foo...'), - contains('No public code changes for foo_platform_interface.'), - contains('Running for foo_bar...'), - contains('No public code changes for foo_platform_interface.'), - ]), - ); - }); - - test('ignores unpublished changes to interface packages', () async { - final Directory pluginGroupDir = packagesDir.childDirectory('foo'); - final RepositoryPackage appFacing = createFakePlugin('foo', pluginGroupDir); - final RepositoryPackage implementation = - createFakePlugin('foo_bar', pluginGroupDir); - final RepositoryPackage platformInterface = - createFakePlugin('foo_platform_interface', pluginGroupDir); - - final String changedFileOutput = [ - appFacing.libDirectory.childFile('foo.dart'), - implementation.libDirectory.childFile('foo.dart'), - platformInterface.pubspecFile, - platformInterface.libDirectory.childFile('foo.dart'), - ].map((File file) => file.path).join('\n'); - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: changedFileOutput), - ]; - // Simulate no change to the version in the interface's pubspec.yaml. - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: platformInterface.pubspecFile.readAsStringSync()), - ]; - - final List output = - await runCapturingPrint(runner, ['federation-safety-check']); - - expect( - output, - containsAllInOrder([ - contains('Running for foo/foo...'), - contains('No published changes for foo_platform_interface.'), - contains('Running for foo_bar...'), - contains('No published changes for foo_platform_interface.'), - ]), - ); - }); - - test('allows things that look like mass changes, with warning', () async { - final Directory pluginGroupDir = packagesDir.childDirectory('foo'); - final RepositoryPackage appFacing = createFakePlugin('foo', pluginGroupDir); - final RepositoryPackage implementation = - createFakePlugin('foo_bar', pluginGroupDir); - final RepositoryPackage platformInterface = - createFakePlugin('foo_platform_interface', pluginGroupDir); - - final RepositoryPackage otherPlugin1 = createFakePlugin('bar', packagesDir); - final RepositoryPackage otherPlugin2 = createFakePlugin('baz', packagesDir); - - final String changedFileOutput = [ - appFacing.libDirectory.childFile('foo.dart'), - implementation.libDirectory.childFile('foo.dart'), - platformInterface.pubspecFile, - platformInterface.libDirectory.childFile('foo.dart'), - otherPlugin1.libDirectory.childFile('bar.dart'), - otherPlugin2.libDirectory.childFile('baz.dart'), - ].map((File file) => file.path).join('\n'); - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: changedFileOutput), - ]; - - final List output = - await runCapturingPrint(runner, ['federation-safety-check']); - - expect( - output, - containsAllInOrder([ - contains('Running for foo/foo...'), - contains( - 'Ignoring potentially dangerous change, as this appears to be a mass change.'), - contains('Running for foo_bar...'), - contains( - 'Ignoring potentially dangerous change, as this appears to be a mass change.'), - contains('Ran for 2 package(s) (2 with warnings)'), - ]), - ); - }); - - test('handles top-level files that match federated package heuristics', - () async { - final RepositoryPackage plugin = createFakePlugin('foo', packagesDir); - - final String changedFileOutput = [ - // This should be picked up as a change to 'foo', and not crash. - plugin.directory.childFile('foo_bar.baz'), - ].map((File file) => file.path).join('\n'); - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: changedFileOutput), - ]; - - final List output = - await runCapturingPrint(runner, ['federation-safety-check']); - - expect( - output, - containsAllInOrder([ - contains('Running for foo...'), - ]), - ); - }); - - test('handles deletion of an entire plugin', () async { - // Simulate deletion, in the form of diffs for packages that don't exist in - // the filesystem. - final String changedFileOutput = [ - packagesDir.childDirectory('foo').childFile('pubspec.yaml'), - packagesDir - .childDirectory('foo') - .childDirectory('lib') - .childFile('foo.dart'), - packagesDir - .childDirectory('foo_platform_interface') - .childFile('pubspec.yaml'), - packagesDir - .childDirectory('foo_platform_interface') - .childDirectory('lib') - .childFile('foo.dart'), - packagesDir.childDirectory('foo_web').childFile('pubspec.yaml'), - packagesDir - .childDirectory('foo_web') - .childDirectory('lib') - .childFile('foo.dart'), - ].map((File file) => file.path).join('\n'); - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: changedFileOutput), - ]; - - final List output = - await runCapturingPrint(runner, ['federation-safety-check']); - - expect( - output, - containsAllInOrder([ - contains('Ran for 0 package(s)'), - ]), - ); - }); -} diff --git a/script/tool/test/firebase_test_lab_command_test.dart b/script/tool/test/firebase_test_lab_command_test.dart deleted file mode 100644 index 68ea62b2334f..000000000000 --- a/script/tool/test/firebase_test_lab_command_test.dart +++ /dev/null @@ -1,795 +0,0 @@ -// Copyright 2013 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. - -import 'dart:io'; - -import 'package:args/command_runner.dart'; -import 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:flutter_plugin_tools/src/common/core.dart'; -import 'package:flutter_plugin_tools/src/common/file_utils.dart'; -import 'package:flutter_plugin_tools/src/firebase_test_lab_command.dart'; -import 'package:path/path.dart' as p; -import 'package:test/test.dart'; - -import 'mocks.dart'; -import 'util.dart'; - -void main() { - group('FirebaseTestLabCommand', () { - FileSystem fileSystem; - late MockPlatform mockPlatform; - late Directory packagesDir; - late CommandRunner runner; - late RecordingProcessRunner processRunner; - - setUp(() { - fileSystem = MemoryFileSystem(); - mockPlatform = MockPlatform(); - packagesDir = createPackagesDirectory(fileSystem: fileSystem); - processRunner = RecordingProcessRunner(); - final FirebaseTestLabCommand command = FirebaseTestLabCommand( - packagesDir, - processRunner: processRunner, - platform: mockPlatform, - ); - - runner = CommandRunner( - 'firebase_test_lab_command', 'Test for $FirebaseTestLabCommand'); - runner.addCommand(command); - }); - - void writeJavaTestFile(RepositoryPackage plugin, String relativeFilePath, - {String runnerClass = 'FlutterTestRunner'}) { - childFileWithSubcomponents( - plugin.directory, p.posix.split(relativeFilePath)) - .writeAsStringSync(''' -@DartIntegrationTest -@RunWith($runnerClass.class) -public class MainActivityTest { - @Rule - public ActivityTestRule rule = new ActivityTestRule<>(FlutterActivity.class); -} -'''); - } - - test('fails if gcloud auth fails', () async { - processRunner.mockProcessesForExecutable['gcloud'] = [ - MockProcess(exitCode: 1) - ]; - - const String javaTestFileRelativePath = - 'example/android/app/src/androidTest/MainActivityTest.java'; - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, extraFiles: [ - 'example/integration_test/foo_test.dart', - 'example/android/gradlew', - javaTestFileRelativePath, - ]); - writeJavaTestFile(plugin, javaTestFileRelativePath); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['firebase-test-lab'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Unable to activate gcloud account.'), - ])); - }); - - test('retries gcloud set', () async { - processRunner.mockProcessesForExecutable['gcloud'] = [ - MockProcess(), // auth - MockProcess(exitCode: 1), // config - ]; - - const String javaTestFileRelativePath = - 'example/android/app/src/androidTest/MainActivityTest.java'; - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, extraFiles: [ - 'example/integration_test/foo_test.dart', - 'example/android/gradlew', - javaTestFileRelativePath, - ]); - writeJavaTestFile(plugin, javaTestFileRelativePath); - - final List output = - await runCapturingPrint(runner, ['firebase-test-lab']); - - expect( - output, - containsAllInOrder([ - contains( - 'Warning: gcloud config set returned a non-zero exit code. Continuing anyway.'), - ])); - }); - - test('only runs gcloud configuration once', () async { - const String javaTestFileRelativePath = - 'example/android/app/src/androidTest/MainActivityTest.java'; - final RepositoryPackage plugin1 = - createFakePlugin('plugin1', packagesDir, extraFiles: [ - 'test/plugin_test.dart', - 'example/integration_test/foo_test.dart', - 'example/android/gradlew', - javaTestFileRelativePath, - ]); - writeJavaTestFile(plugin1, javaTestFileRelativePath); - final RepositoryPackage plugin2 = - createFakePlugin('plugin2', packagesDir, extraFiles: [ - 'test/plugin_test.dart', - 'example/integration_test/bar_test.dart', - 'example/android/gradlew', - javaTestFileRelativePath, - ]); - writeJavaTestFile(plugin2, javaTestFileRelativePath); - - final List output = await runCapturingPrint(runner, [ - 'firebase-test-lab', - '--device', - 'model=redfin,version=30', - '--device', - 'model=seoul,version=26', - '--test-run-id', - 'testRunId', - '--build-id', - 'buildId', - ]); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin1'), - contains('Firebase project configured.'), - contains('Testing example/integration_test/foo_test.dart...'), - contains('Running for plugin2'), - contains('Testing example/integration_test/bar_test.dart...'), - ]), - ); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - 'gcloud', - 'auth activate-service-account --key-file=${Platform.environment['HOME']}/gcloud-service-key.json' - .split(' '), - null), - ProcessCall( - 'gcloud', 'config set project flutter-cirrus'.split(' '), null), - ProcessCall( - '/packages/plugin1/example/android/gradlew', - 'app:assembleAndroidTest -Pverbose=true'.split(' '), - '/packages/plugin1/example/android'), - ProcessCall( - '/packages/plugin1/example/android/gradlew', - 'app:assembleDebug -Pverbose=true -Ptarget=/packages/plugin1/example/integration_test/foo_test.dart' - .split(' '), - '/packages/plugin1/example/android'), - ProcessCall( - 'gcloud', - 'firebase test android run --type instrumentation --app build/app/outputs/apk/debug/app-debug.apk --test build/app/outputs/apk/androidTest/debug/app-debug-androidTest.apk --timeout 7m --results-bucket=gs://flutter_cirrus_testlab --results-dir=plugins_android_test/plugin1/buildId/testRunId/example/0/ --device model=redfin,version=30 --device model=seoul,version=26' - .split(' '), - '/packages/plugin1/example'), - ProcessCall( - '/packages/plugin2/example/android/gradlew', - 'app:assembleAndroidTest -Pverbose=true'.split(' '), - '/packages/plugin2/example/android'), - ProcessCall( - '/packages/plugin2/example/android/gradlew', - 'app:assembleDebug -Pverbose=true -Ptarget=/packages/plugin2/example/integration_test/bar_test.dart' - .split(' '), - '/packages/plugin2/example/android'), - ProcessCall( - 'gcloud', - 'firebase test android run --type instrumentation --app build/app/outputs/apk/debug/app-debug.apk --test build/app/outputs/apk/androidTest/debug/app-debug-androidTest.apk --timeout 7m --results-bucket=gs://flutter_cirrus_testlab --results-dir=plugins_android_test/plugin2/buildId/testRunId/example/0/ --device model=redfin,version=30 --device model=seoul,version=26' - .split(' '), - '/packages/plugin2/example'), - ]), - ); - }); - - test('runs integration tests', () async { - const String javaTestFileRelativePath = - 'example/android/app/src/androidTest/MainActivityTest.java'; - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, extraFiles: [ - 'test/plugin_test.dart', - 'example/integration_test/bar_test.dart', - 'example/integration_test/foo_test.dart', - 'example/integration_test/should_not_run.dart', - 'example/android/gradlew', - javaTestFileRelativePath, - ]); - writeJavaTestFile(plugin, javaTestFileRelativePath); - - final List output = await runCapturingPrint(runner, [ - 'firebase-test-lab', - '--device', - 'model=redfin,version=30', - '--device', - 'model=seoul,version=26', - '--test-run-id', - 'testRunId', - '--build-id', - 'buildId', - ]); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('Firebase project configured.'), - contains('Testing example/integration_test/bar_test.dart...'), - contains('Testing example/integration_test/foo_test.dart...'), - ]), - ); - expect(output, isNot(contains('test/plugin_test.dart'))); - expect(output, - isNot(contains('example/integration_test/should_not_run.dart'))); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - 'gcloud', - 'auth activate-service-account --key-file=${Platform.environment['HOME']}/gcloud-service-key.json' - .split(' '), - null), - ProcessCall( - 'gcloud', 'config set project flutter-cirrus'.split(' '), null), - ProcessCall( - '/packages/plugin/example/android/gradlew', - 'app:assembleAndroidTest -Pverbose=true'.split(' '), - '/packages/plugin/example/android'), - ProcessCall( - '/packages/plugin/example/android/gradlew', - 'app:assembleDebug -Pverbose=true -Ptarget=/packages/plugin/example/integration_test/bar_test.dart' - .split(' '), - '/packages/plugin/example/android'), - ProcessCall( - 'gcloud', - 'firebase test android run --type instrumentation --app build/app/outputs/apk/debug/app-debug.apk --test build/app/outputs/apk/androidTest/debug/app-debug-androidTest.apk --timeout 7m --results-bucket=gs://flutter_cirrus_testlab --results-dir=plugins_android_test/plugin/buildId/testRunId/example/0/ --device model=redfin,version=30 --device model=seoul,version=26' - .split(' '), - '/packages/plugin/example'), - ProcessCall( - '/packages/plugin/example/android/gradlew', - 'app:assembleDebug -Pverbose=true -Ptarget=/packages/plugin/example/integration_test/foo_test.dart' - .split(' '), - '/packages/plugin/example/android'), - ProcessCall( - 'gcloud', - 'firebase test android run --type instrumentation --app build/app/outputs/apk/debug/app-debug.apk --test build/app/outputs/apk/androidTest/debug/app-debug-androidTest.apk --timeout 7m --results-bucket=gs://flutter_cirrus_testlab --results-dir=plugins_android_test/plugin/buildId/testRunId/example/1/ --device model=redfin,version=30 --device model=seoul,version=26' - .split(' '), - '/packages/plugin/example'), - ]), - ); - }); - - test('runs for all examples', () async { - const List examples = ['example1', 'example2']; - const String javaTestFileExampleRelativePath = - 'android/app/src/androidTest/MainActivityTest.java'; - final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir, - examples: examples, - extraFiles: [ - for (final String example in examples) ...[ - 'example/$example/integration_test/a_test.dart', - 'example/$example/android/gradlew', - 'example/$example/$javaTestFileExampleRelativePath', - ], - ]); - for (final String example in examples) { - writeJavaTestFile( - plugin, 'example/$example/$javaTestFileExampleRelativePath'); - } - - final List output = await runCapturingPrint(runner, [ - 'firebase-test-lab', - '--device', - 'model=redfin,version=30', - '--device', - 'model=seoul,version=26', - '--test-run-id', - 'testRunId', - '--build-id', - 'buildId', - ]); - - expect( - output, - containsAllInOrder([ - contains('Testing example/example1/integration_test/a_test.dart...'), - contains('Testing example/example2/integration_test/a_test.dart...'), - ]), - ); - - expect( - processRunner.recordedCalls, - containsAll([ - ProcessCall( - '/packages/plugin/example/example1/android/gradlew', - 'app:assembleDebug -Pverbose=true -Ptarget=/packages/plugin/example/example1/integration_test/a_test.dart' - .split(' '), - '/packages/plugin/example/example1/android'), - ProcessCall( - 'gcloud', - 'firebase test android run --type instrumentation --app build/app/outputs/apk/debug/app-debug.apk --test build/app/outputs/apk/androidTest/debug/app-debug-androidTest.apk --timeout 7m --results-bucket=gs://flutter_cirrus_testlab --results-dir=plugins_android_test/plugin/buildId/testRunId/example1/0/ --device model=redfin,version=30 --device model=seoul,version=26' - .split(' '), - '/packages/plugin/example/example1'), - ProcessCall( - '/packages/plugin/example/example2/android/gradlew', - 'app:assembleDebug -Pverbose=true -Ptarget=/packages/plugin/example/example2/integration_test/a_test.dart' - .split(' '), - '/packages/plugin/example/example2/android'), - ProcessCall( - 'gcloud', - 'firebase test android run --type instrumentation --app build/app/outputs/apk/debug/app-debug.apk --test build/app/outputs/apk/androidTest/debug/app-debug-androidTest.apk --timeout 7m --results-bucket=gs://flutter_cirrus_testlab --results-dir=plugins_android_test/plugin/buildId/testRunId/example2/0/ --device model=redfin,version=30 --device model=seoul,version=26' - .split(' '), - '/packages/plugin/example/example2'), - ]), - ); - }); - - test('fails if a test fails twice', () async { - const String javaTestFileRelativePath = - 'example/android/app/src/androidTest/MainActivityTest.java'; - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, extraFiles: [ - 'example/integration_test/bar_test.dart', - 'example/integration_test/foo_test.dart', - 'example/android/gradlew', - javaTestFileRelativePath, - ]); - writeJavaTestFile(plugin, javaTestFileRelativePath); - - processRunner.mockProcessesForExecutable['gcloud'] = [ - MockProcess(), // auth - MockProcess(), // config - MockProcess(exitCode: 1), // integration test #1 - MockProcess(exitCode: 1), // integration test #1 retry - MockProcess(), // integration test #2 - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, - [ - 'firebase-test-lab', - '--device', - 'model=redfin,version=30', - ], - errorHandler: (Error e) { - commandError = e; - }, - ); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Testing example/integration_test/bar_test.dart...'), - contains('Testing example/integration_test/foo_test.dart...'), - contains('plugin:\n' - ' example/integration_test/bar_test.dart failed tests'), - ]), - ); - }); - - test('passes with warning if a test fails once, then passes on retry', - () async { - const String javaTestFileRelativePath = - 'example/android/app/src/androidTest/MainActivityTest.java'; - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, extraFiles: [ - 'example/integration_test/bar_test.dart', - 'example/integration_test/foo_test.dart', - 'example/android/gradlew', - javaTestFileRelativePath, - ]); - writeJavaTestFile(plugin, javaTestFileRelativePath); - - processRunner.mockProcessesForExecutable['gcloud'] = [ - MockProcess(), // auth - MockProcess(), // config - MockProcess(exitCode: 1), // integration test #1 - MockProcess(), // integration test #1 retry - MockProcess(), // integration test #2 - ]; - - final List output = await runCapturingPrint(runner, [ - 'firebase-test-lab', - '--device', - 'model=redfin,version=30', - ]); - - expect( - output, - containsAllInOrder([ - contains('Testing example/integration_test/bar_test.dart...'), - contains('bar_test.dart failed on attempt 1. Retrying...'), - contains('Testing example/integration_test/foo_test.dart...'), - contains('Ran for 1 package(s) (1 with warnings)'), - ]), - ); - }); - - test('fails for packages with no androidTest directory', () async { - createFakePlugin('plugin', packagesDir, extraFiles: [ - 'example/integration_test/foo_test.dart', - 'example/android/gradlew', - ]); - - Error? commandError; - final List output = await runCapturingPrint( - runner, - [ - 'firebase-test-lab', - '--device', - 'model=redfin,version=30', - ], - errorHandler: (Error e) { - commandError = e; - }, - ); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('No androidTest directory found.'), - contains('The following packages had errors:'), - contains('plugin:\n' - ' No tests ran (use --exclude if this is intentional).'), - ]), - ); - }); - - test('fails for packages with no integration test files', () async { - const String javaTestFileRelativePath = - 'example/android/app/src/androidTest/MainActivityTest.java'; - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, extraFiles: [ - 'example/android/gradlew', - javaTestFileRelativePath, - ]); - writeJavaTestFile(plugin, javaTestFileRelativePath); - - Error? commandError; - final List output = await runCapturingPrint( - runner, - [ - 'firebase-test-lab', - '--device', - 'model=redfin,version=30', - ], - errorHandler: (Error e) { - commandError = e; - }, - ); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('No integration tests were run'), - contains('The following packages had errors:'), - contains('plugin:\n' - ' No tests ran (use --exclude if this is intentional).'), - ]), - ); - }); - - test('fails for packages with no integration_test runner', () async { - const String javaTestFileRelativePath = - 'example/android/app/src/androidTest/MainActivityTest.java'; - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, extraFiles: [ - 'test/plugin_test.dart', - 'example/integration_test/bar_test.dart', - 'example/integration_test/foo_test.dart', - 'example/integration_test/should_not_run.dart', - 'example/android/gradlew', - javaTestFileRelativePath, - ]); - // Use the wrong @RunWith annotation. - writeJavaTestFile(plugin, javaTestFileRelativePath, - runnerClass: 'AndroidJUnit4.class'); - - Error? commandError; - final List output = await runCapturingPrint( - runner, - [ - 'firebase-test-lab', - '--device', - 'model=redfin,version=30', - ], - errorHandler: (Error e) { - commandError = e; - }, - ); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('No integration_test runner found. ' - 'See the integration_test package README for setup instructions.'), - contains('plugin:\n' - ' No integration_test runner.'), - ]), - ); - }); - - test('skips packages with no android directory', () async { - createFakePackage('package', packagesDir, extraFiles: [ - 'example/integration_test/foo_test.dart', - ]); - - final List output = await runCapturingPrint(runner, [ - 'firebase-test-lab', - '--device', - 'model=redfin,version=30', - ]); - - expect( - output, - containsAllInOrder([ - contains('Running for package'), - contains('No examples support Android'), - ]), - ); - expect(output, - isNot(contains('Testing example/integration_test/foo_test.dart...'))); - - expect( - processRunner.recordedCalls, - orderedEquals([]), - ); - }); - - test('builds if gradlew is missing', () async { - const String javaTestFileRelativePath = - 'example/android/app/src/androidTest/MainActivityTest.java'; - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, extraFiles: [ - 'example/integration_test/foo_test.dart', - javaTestFileRelativePath, - ]); - writeJavaTestFile(plugin, javaTestFileRelativePath); - - final List output = await runCapturingPrint(runner, [ - 'firebase-test-lab', - '--device', - 'model=redfin,version=30', - '--test-run-id', - 'testRunId', - '--build-id', - 'buildId', - ]); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('Running flutter build apk...'), - contains('Firebase project configured.'), - contains('Testing example/integration_test/foo_test.dart...'), - ]), - ); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - 'flutter', - 'build apk'.split(' '), - '/packages/plugin/example/android', - ), - ProcessCall( - 'gcloud', - 'auth activate-service-account --key-file=${Platform.environment['HOME']}/gcloud-service-key.json' - .split(' '), - null), - ProcessCall( - 'gcloud', 'config set project flutter-cirrus'.split(' '), null), - ProcessCall( - '/packages/plugin/example/android/gradlew', - 'app:assembleAndroidTest -Pverbose=true'.split(' '), - '/packages/plugin/example/android'), - ProcessCall( - '/packages/plugin/example/android/gradlew', - 'app:assembleDebug -Pverbose=true -Ptarget=/packages/plugin/example/integration_test/foo_test.dart' - .split(' '), - '/packages/plugin/example/android'), - ProcessCall( - 'gcloud', - 'firebase test android run --type instrumentation --app build/app/outputs/apk/debug/app-debug.apk --test build/app/outputs/apk/androidTest/debug/app-debug-androidTest.apk --timeout 7m --results-bucket=gs://flutter_cirrus_testlab --results-dir=plugins_android_test/plugin/buildId/testRunId/example/0/ --device model=redfin,version=30' - .split(' '), - '/packages/plugin/example'), - ]), - ); - }); - - test('fails if building to generate gradlew fails', () async { - const String javaTestFileRelativePath = - 'example/android/app/src/androidTest/MainActivityTest.java'; - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, extraFiles: [ - 'example/integration_test/foo_test.dart', - javaTestFileRelativePath, - ]); - writeJavaTestFile(plugin, javaTestFileRelativePath); - - processRunner.mockProcessesForExecutable['flutter'] = [ - MockProcess(exitCode: 1) // flutter build - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, - [ - 'firebase-test-lab', - '--device', - 'model=redfin,version=30', - ], - errorHandler: (Error e) { - commandError = e; - }, - ); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Unable to build example apk'), - ])); - }); - - test('fails if assembleAndroidTest fails', () async { - const String javaTestFileRelativePath = - 'example/android/app/src/androidTest/MainActivityTest.java'; - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, extraFiles: [ - 'example/integration_test/foo_test.dart', - javaTestFileRelativePath, - ]); - writeJavaTestFile(plugin, javaTestFileRelativePath); - - final String gradlewPath = plugin - .getExamples() - .first - .platformDirectory(FlutterPlatform.android) - .childFile('gradlew') - .path; - processRunner.mockProcessesForExecutable[gradlewPath] = [ - MockProcess(exitCode: 1) - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, - [ - 'firebase-test-lab', - '--device', - 'model=redfin,version=30', - ], - errorHandler: (Error e) { - commandError = e; - }, - ); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Unable to assemble androidTest'), - ])); - }); - - test('fails if assembleDebug fails', () async { - const String javaTestFileRelativePath = - 'example/android/app/src/androidTest/MainActivityTest.java'; - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, extraFiles: [ - 'example/integration_test/foo_test.dart', - javaTestFileRelativePath, - ]); - writeJavaTestFile(plugin, javaTestFileRelativePath); - - final String gradlewPath = plugin - .getExamples() - .first - .platformDirectory(FlutterPlatform.android) - .childFile('gradlew') - .path; - processRunner.mockProcessesForExecutable[gradlewPath] = [ - MockProcess(), // assembleAndroidTest - MockProcess(exitCode: 1), // assembleDebug - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, - [ - 'firebase-test-lab', - '--device', - 'model=redfin,version=30', - ], - errorHandler: (Error e) { - commandError = e; - }, - ); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Could not build example/integration_test/foo_test.dart'), - contains('The following packages had errors:'), - contains(' plugin:\n' - ' example/integration_test/foo_test.dart failed to build'), - ])); - }); - - test('experimental flag', () async { - const String javaTestFileRelativePath = - 'example/android/app/src/androidTest/MainActivityTest.java'; - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, extraFiles: [ - 'example/integration_test/foo_test.dart', - 'example/android/gradlew', - javaTestFileRelativePath, - ]); - writeJavaTestFile(plugin, javaTestFileRelativePath); - - await runCapturingPrint(runner, [ - 'firebase-test-lab', - '--device', - 'model=redfin,version=30', - '--test-run-id', - 'testRunId', - '--build-id', - 'buildId', - '--enable-experiment=exp1', - ]); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - 'gcloud', - 'auth activate-service-account --key-file=${Platform.environment['HOME']}/gcloud-service-key.json' - .split(' '), - null), - ProcessCall( - 'gcloud', 'config set project flutter-cirrus'.split(' '), null), - ProcessCall( - '/packages/plugin/example/android/gradlew', - 'app:assembleAndroidTest -Pverbose=true -Pextra-front-end-options=--enable-experiment%3Dexp1 -Pextra-gen-snapshot-options=--enable-experiment%3Dexp1' - .split(' '), - '/packages/plugin/example/android'), - ProcessCall( - '/packages/plugin/example/android/gradlew', - 'app:assembleDebug -Pverbose=true -Ptarget=/packages/plugin/example/integration_test/foo_test.dart -Pextra-front-end-options=--enable-experiment%3Dexp1 -Pextra-gen-snapshot-options=--enable-experiment%3Dexp1' - .split(' '), - '/packages/plugin/example/android'), - ProcessCall( - 'gcloud', - 'firebase test android run --type instrumentation --app build/app/outputs/apk/debug/app-debug.apk --test build/app/outputs/apk/androidTest/debug/app-debug-androidTest.apk --timeout 7m --results-bucket=gs://flutter_cirrus_testlab --results-dir=plugins_android_test/plugin/buildId/testRunId/example/0/ --device model=redfin,version=30' - .split(' '), - '/packages/plugin/example'), - ]), - ); - }); - }); -} diff --git a/script/tool/test/fix_command_test.dart b/script/tool/test/fix_command_test.dart deleted file mode 100644 index 16061d2206cd..000000000000 --- a/script/tool/test/fix_command_test.dart +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright 2013 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. - -import 'dart:io' as io; - -import 'package:args/command_runner.dart'; -import 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:flutter_plugin_tools/src/common/core.dart'; -import 'package:flutter_plugin_tools/src/fix_command.dart'; -import 'package:test/test.dart'; - -import 'mocks.dart'; -import 'util.dart'; - -void main() { - late FileSystem fileSystem; - late MockPlatform mockPlatform; - late Directory packagesDir; - late RecordingProcessRunner processRunner; - late CommandRunner runner; - - setUp(() { - fileSystem = MemoryFileSystem(); - mockPlatform = MockPlatform(); - packagesDir = createPackagesDirectory(fileSystem: fileSystem); - processRunner = RecordingProcessRunner(); - final FixCommand command = FixCommand( - packagesDir, - processRunner: processRunner, - platform: mockPlatform, - ); - - runner = CommandRunner('fix_command', 'Test for fix_command'); - runner.addCommand(command); - }); - - test('runs fix in top-level packages and subpackages', () async { - final RepositoryPackage package = createFakePackage('a', packagesDir); - final RepositoryPackage plugin = createFakePlugin('b', packagesDir); - - await runCapturingPrint(runner, ['fix']); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall('dart', const ['fix', '--apply'], package.path), - ProcessCall('dart', const ['fix', '--apply'], - package.getExamples().first.path), - ProcessCall('dart', const ['fix', '--apply'], plugin.path), - ProcessCall('dart', const ['fix', '--apply'], - plugin.getExamples().first.path), - ])); - }); - - test('fails if "dart fix" fails', () async { - createFakePlugin('foo', packagesDir); - - processRunner.mockProcessesForExecutable['dart'] = [ - MockProcess(exitCode: 1), - ]; - - Error? commandError; - final List output = await runCapturingPrint(runner, ['fix'], - errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Unable to automatically fix package.'), - ]), - ); - }); -} diff --git a/script/tool/test/format_command_test.dart b/script/tool/test/format_command_test.dart deleted file mode 100644 index 634a996bccc6..000000000000 --- a/script/tool/test/format_command_test.dart +++ /dev/null @@ -1,663 +0,0 @@ -// Copyright 2013 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. - -import 'dart:io' as io; - -import 'package:args/command_runner.dart'; -import 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:flutter_plugin_tools/src/common/core.dart'; -import 'package:flutter_plugin_tools/src/common/file_utils.dart'; -import 'package:flutter_plugin_tools/src/format_command.dart'; -import 'package:path/path.dart' as p; -import 'package:test/test.dart'; - -import 'mocks.dart'; -import 'util.dart'; - -void main() { - late FileSystem fileSystem; - late MockPlatform mockPlatform; - late Directory packagesDir; - late RecordingProcessRunner processRunner; - late FormatCommand analyzeCommand; - late CommandRunner runner; - late String javaFormatPath; - - setUp(() { - fileSystem = MemoryFileSystem(); - mockPlatform = MockPlatform(); - packagesDir = createPackagesDirectory(fileSystem: fileSystem); - processRunner = RecordingProcessRunner(); - analyzeCommand = FormatCommand( - packagesDir, - processRunner: processRunner, - platform: mockPlatform, - ); - - // Create the java formatter file that the command checks for, to avoid - // a download. - final p.Context path = analyzeCommand.path; - javaFormatPath = path.join(path.dirname(path.fromUri(mockPlatform.script)), - 'google-java-format-1.3-all-deps.jar'); - fileSystem.file(javaFormatPath).createSync(recursive: true); - - runner = CommandRunner('format_command', 'Test for format_command'); - runner.addCommand(analyzeCommand); - }); - - /// Returns a modified version of a list of [relativePaths] that are relative - /// to [package] to instead be relative to [packagesDir]. - List getPackagesDirRelativePaths( - RepositoryPackage package, List relativePaths) { - final p.Context path = analyzeCommand.path; - final String relativeBase = - path.relative(package.path, from: packagesDir.path); - return relativePaths - .map((String relativePath) => path.join(relativeBase, relativePath)) - .toList(); - } - - /// Returns a list of [count] relative paths to pass to [createFakePlugin] - /// or [createFakePackage] with name [packageName] such that each path will - /// be 99 characters long relative to [packagesDir]. - /// - /// This is for each of testing batching, since it means each file will - /// consume 100 characters of the batch length. - List get99CharacterPathExtraFiles(String packageName, int count) { - final int padding = 99 - - packageName.length - - 1 - // the path separator after the package name - 1 - // the path separator after the padding - 10; // the file name - const int filenameBase = 10000; - - final p.Context path = analyzeCommand.path; - return [ - for (int i = filenameBase; i < filenameBase + count; ++i) - path.join('a' * padding, '$i.dart'), - ]; - } - - test('formats .dart files', () async { - const List files = [ - 'lib/a.dart', - 'lib/src/b.dart', - 'lib/src/c.dart', - ]; - final RepositoryPackage plugin = createFakePlugin( - 'a_plugin', - packagesDir, - extraFiles: files, - ); - - await runCapturingPrint(runner, ['format']); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - 'dart', - ['format', ...getPackagesDirRelativePaths(plugin, files)], - packagesDir.path), - ])); - }); - - test('does not format .dart files with pragma', () async { - const List formattedFiles = [ - 'lib/a.dart', - 'lib/src/b.dart', - 'lib/src/c.dart', - ]; - const String unformattedFile = 'lib/src/d.dart'; - final RepositoryPackage plugin = createFakePlugin( - 'a_plugin', - packagesDir, - extraFiles: [ - ...formattedFiles, - unformattedFile, - ], - ); - - final p.Context posixContext = p.posix; - childFileWithSubcomponents( - plugin.directory, posixContext.split(unformattedFile)) - .writeAsStringSync( - '// copyright bla bla\n// This file is hand-formatted.\ncode...'); - - await runCapturingPrint(runner, ['format']); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - 'dart', - [ - 'format', - ...getPackagesDirRelativePaths(plugin, formattedFiles) - ], - packagesDir.path), - ])); - }); - - test('fails if dart format fails', () async { - const List files = [ - 'lib/a.dart', - 'lib/src/b.dart', - 'lib/src/c.dart', - ]; - createFakePlugin('a_plugin', packagesDir, extraFiles: files); - - processRunner.mockProcessesForExecutable['dart'] = [ - MockProcess(exitCode: 1) - ]; - Error? commandError; - final List output = await runCapturingPrint( - runner, ['format'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Failed to format Dart files: exit code 1.'), - ])); - }); - - test('formats .java files', () async { - const List files = [ - 'android/src/main/java/io/flutter/plugins/a_plugin/a.java', - 'android/src/main/java/io/flutter/plugins/a_plugin/b.java', - ]; - final RepositoryPackage plugin = createFakePlugin( - 'a_plugin', - packagesDir, - extraFiles: files, - ); - - await runCapturingPrint(runner, ['format']); - - expect( - processRunner.recordedCalls, - orderedEquals([ - const ProcessCall('java', ['-version'], null), - ProcessCall( - 'java', - [ - '-jar', - javaFormatPath, - '--replace', - ...getPackagesDirRelativePaths(plugin, files) - ], - packagesDir.path), - ])); - }); - - test('fails with a clear message if Java is not in the path', () async { - const List files = [ - 'android/src/main/java/io/flutter/plugins/a_plugin/a.java', - 'android/src/main/java/io/flutter/plugins/a_plugin/b.java', - ]; - createFakePlugin('a_plugin', packagesDir, extraFiles: files); - - processRunner.mockProcessesForExecutable['java'] = [ - MockProcess(exitCode: 1) - ]; - Error? commandError; - final List output = await runCapturingPrint( - runner, ['format'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains( - 'Unable to run "java". Make sure that it is in your path, or ' - 'provide a full path with --java.'), - ])); - }); - - test('fails if Java formatter fails', () async { - const List files = [ - 'android/src/main/java/io/flutter/plugins/a_plugin/a.java', - 'android/src/main/java/io/flutter/plugins/a_plugin/b.java', - ]; - createFakePlugin('a_plugin', packagesDir, extraFiles: files); - - processRunner.mockProcessesForExecutable['java'] = [ - MockProcess(), // check for working java - MockProcess(exitCode: 1), // format - ]; - Error? commandError; - final List output = await runCapturingPrint( - runner, ['format'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Failed to format Java files: exit code 1.'), - ])); - }); - - test('honors --java flag', () async { - const List files = [ - 'android/src/main/java/io/flutter/plugins/a_plugin/a.java', - 'android/src/main/java/io/flutter/plugins/a_plugin/b.java', - ]; - final RepositoryPackage plugin = createFakePlugin( - 'a_plugin', - packagesDir, - extraFiles: files, - ); - - await runCapturingPrint(runner, ['format', '--java=/path/to/java']); - - expect( - processRunner.recordedCalls, - orderedEquals([ - const ProcessCall('/path/to/java', ['--version'], null), - ProcessCall( - '/path/to/java', - [ - '-jar', - javaFormatPath, - '--replace', - ...getPackagesDirRelativePaths(plugin, files) - ], - packagesDir.path), - ])); - }); - - test('formats c-ish files', () async { - const List files = [ - 'ios/Classes/Foo.h', - 'ios/Classes/Foo.m', - 'linux/foo_plugin.cc', - 'macos/Classes/Foo.h', - 'macos/Classes/Foo.mm', - 'windows/foo_plugin.cpp', - ]; - final RepositoryPackage plugin = createFakePlugin( - 'a_plugin', - packagesDir, - extraFiles: files, - ); - - await runCapturingPrint(runner, ['format']); - - expect( - processRunner.recordedCalls, - orderedEquals([ - const ProcessCall('clang-format', ['--version'], null), - ProcessCall( - 'clang-format', - [ - '-i', - '--style=file', - ...getPackagesDirRelativePaths(plugin, files) - ], - packagesDir.path), - ])); - }); - - test('fails with a clear message if clang-format is not in the path', - () async { - const List files = [ - 'linux/foo_plugin.cc', - 'macos/Classes/Foo.h', - ]; - createFakePlugin('a_plugin', packagesDir, extraFiles: files); - - processRunner.mockProcessesForExecutable['clang-format'] = [ - MockProcess(exitCode: 1) - ]; - Error? commandError; - final List output = await runCapturingPrint( - runner, ['format'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Unable to run "clang-format". Make sure that it is in your ' - 'path, or provide a full path with --clang-format.'), - ])); - }); - - test('falls back to working clang-format in the path', () async { - const List files = [ - 'linux/foo_plugin.cc', - 'macos/Classes/Foo.h', - ]; - final RepositoryPackage plugin = createFakePlugin( - 'a_plugin', - packagesDir, - extraFiles: files, - ); - - processRunner.mockProcessesForExecutable['clang-format'] = [ - MockProcess(exitCode: 1) - ]; - processRunner.mockProcessesForExecutable['which'] = [ - MockProcess( - stdout: '/usr/local/bin/clang-format\n/path/to/working-clang-format') - ]; - processRunner.mockProcessesForExecutable['/usr/local/bin/clang-format'] = - [MockProcess(exitCode: 1)]; - await runCapturingPrint(runner, ['format']); - - expect( - processRunner.recordedCalls, - containsAll([ - const ProcessCall( - '/path/to/working-clang-format', ['--version'], null), - ProcessCall( - '/path/to/working-clang-format', - [ - '-i', - '--style=file', - ...getPackagesDirRelativePaths(plugin, files) - ], - packagesDir.path), - ])); - }); - - test('honors --clang-format flag', () async { - const List files = [ - 'windows/foo_plugin.cpp', - ]; - final RepositoryPackage plugin = createFakePlugin( - 'a_plugin', - packagesDir, - extraFiles: files, - ); - - await runCapturingPrint( - runner, ['format', '--clang-format=/path/to/clang-format']); - - expect( - processRunner.recordedCalls, - orderedEquals([ - const ProcessCall( - '/path/to/clang-format', ['--version'], null), - ProcessCall( - '/path/to/clang-format', - [ - '-i', - '--style=file', - ...getPackagesDirRelativePaths(plugin, files) - ], - packagesDir.path), - ])); - }); - - test('fails if clang-format fails', () async { - const List files = [ - 'linux/foo_plugin.cc', - 'macos/Classes/Foo.h', - ]; - createFakePlugin('a_plugin', packagesDir, extraFiles: files); - - processRunner.mockProcessesForExecutable['clang-format'] = [ - MockProcess(), // check for working clang-format - MockProcess(exitCode: 1), // format - ]; - Error? commandError; - final List output = await runCapturingPrint( - runner, ['format'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains( - 'Failed to format C, C++, and Objective-C files: exit code 1.'), - ])); - }); - - test('skips known non-repo files', () async { - const List skipFiles = [ - '/example/build/SomeFramework.framework/Headers/SomeFramework.h', - '/example/Pods/APod.framework/Headers/APod.h', - '.dart_tool/internals/foo.cc', - '.dart_tool/internals/Bar.java', - '.dart_tool/internals/baz.dart', - ]; - const List clangFiles = ['ios/Classes/Foo.h']; - const List dartFiles = ['lib/a.dart']; - const List javaFiles = [ - 'android/src/main/java/io/flutter/plugins/a_plugin/a.java' - ]; - final RepositoryPackage plugin = createFakePlugin( - 'a_plugin', - packagesDir, - extraFiles: [ - ...skipFiles, - // Include some files that should be formatted to validate that it's - // correctly filtering even when running the commands. - ...clangFiles, - ...dartFiles, - ...javaFiles, - ], - ); - - await runCapturingPrint(runner, ['format']); - - expect( - processRunner.recordedCalls, - containsAll([ - ProcessCall( - 'clang-format', - [ - '-i', - '--style=file', - ...getPackagesDirRelativePaths(plugin, clangFiles) - ], - packagesDir.path), - ProcessCall( - 'dart', - [ - 'format', - ...getPackagesDirRelativePaths(plugin, dartFiles) - ], - packagesDir.path), - ProcessCall( - 'java', - [ - '-jar', - javaFormatPath, - '--replace', - ...getPackagesDirRelativePaths(plugin, javaFiles) - ], - packagesDir.path), - ])); - }); - - test('fails if files are changed with --fail-on-change', () async { - const List files = [ - 'linux/foo_plugin.cc', - 'macos/Classes/Foo.h', - ]; - createFakePlugin('a_plugin', packagesDir, extraFiles: files); - - const String changedFilePath = 'packages/a_plugin/linux/foo_plugin.cc'; - processRunner.mockProcessesForExecutable['git'] = [ - MockProcess(stdout: changedFilePath), - ]; - - Error? commandError; - final List output = - await runCapturingPrint(runner, ['format', '--fail-on-change'], - errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('These files are not formatted correctly'), - contains(changedFilePath), - contains('patch -p1 < files = [ - 'linux/foo_plugin.cc', - 'macos/Classes/Foo.h', - ]; - createFakePlugin('a_plugin', packagesDir, extraFiles: files); - - processRunner.mockProcessesForExecutable['git'] = [ - MockProcess(exitCode: 1) - ]; - Error? commandError; - final List output = - await runCapturingPrint(runner, ['format', '--fail-on-change'], - errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Unable to determine changed files.'), - ])); - }); - - test('reports git diff failures', () async { - const List files = [ - 'linux/foo_plugin.cc', - 'macos/Classes/Foo.h', - ]; - createFakePlugin('a_plugin', packagesDir, extraFiles: files); - - const String changedFilePath = 'packages/a_plugin/linux/foo_plugin.cc'; - processRunner.mockProcessesForExecutable['git'] = [ - MockProcess(stdout: changedFilePath), // ls-files - MockProcess(exitCode: 1), // diff - ]; - - Error? commandError; - final List output = - await runCapturingPrint(runner, ['format', '--fail-on-change'], - errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('These files are not formatted correctly'), - contains(changedFilePath), - contains('Unable to determine diff.'), - ])); - }); - - test('Batches moderately long file lists on Windows', () async { - mockPlatform.isWindows = true; - - const String pluginName = 'a_plugin'; - // -1 since the command itself takes some length. - const int batchSize = (windowsCommandLineMax ~/ 100) - 1; - - // Make the file list one file longer than would fit in the batch. - final List batch1 = - get99CharacterPathExtraFiles(pluginName, batchSize + 1); - final String extraFile = batch1.removeLast(); - - createFakePlugin( - pluginName, - packagesDir, - extraFiles: [...batch1, extraFile], - ); - - await runCapturingPrint(runner, ['format']); - - // Ensure that it was batched... - expect(processRunner.recordedCalls.length, 2); - // ... and that the spillover into the second batch was only one file. - expect( - processRunner.recordedCalls, - contains( - ProcessCall( - 'dart', - [ - 'format', - '$pluginName\\$extraFile', - ], - packagesDir.path), - )); - }); - - // Validates that the Windows limit--which is much lower than the limit on - // other platforms--isn't being used on all platforms, as that would make - // formatting slower on Linux and macOS. - test('Does not batch moderately long file lists on non-Windows', () async { - const String pluginName = 'a_plugin'; - // -1 since the command itself takes some length. - const int batchSize = (windowsCommandLineMax ~/ 100) - 1; - - // Make the file list one file longer than would fit in a Windows batch. - final List batch = - get99CharacterPathExtraFiles(pluginName, batchSize + 1); - - createFakePlugin( - pluginName, - packagesDir, - extraFiles: batch, - ); - - await runCapturingPrint(runner, ['format']); - - expect(processRunner.recordedCalls.length, 1); - }); - - test('Batches extremely long file lists on non-Windows', () async { - const String pluginName = 'a_plugin'; - // -1 since the command itself takes some length. - const int batchSize = (nonWindowsCommandLineMax ~/ 100) - 1; - - // Make the file list one file longer than would fit in the batch. - final List batch1 = - get99CharacterPathExtraFiles(pluginName, batchSize + 1); - final String extraFile = batch1.removeLast(); - - createFakePlugin( - pluginName, - packagesDir, - extraFiles: [...batch1, extraFile], - ); - - await runCapturingPrint(runner, ['format']); - - // Ensure that it was batched... - expect(processRunner.recordedCalls.length, 2); - // ... and that the spillover into the second batch was only one file. - expect( - processRunner.recordedCalls, - contains( - ProcessCall( - 'dart', - [ - 'format', - '$pluginName/$extraFile', - ], - packagesDir.path), - )); - }); -} diff --git a/script/tool/test/license_check_command_test.dart b/script/tool/test/license_check_command_test.dart deleted file mode 100644 index 09841df74e70..000000000000 --- a/script/tool/test/license_check_command_test.dart +++ /dev/null @@ -1,613 +0,0 @@ -// Copyright 2013 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. - -import 'package:args/command_runner.dart'; -import 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:flutter_plugin_tools/src/common/core.dart'; -import 'package:flutter_plugin_tools/src/license_check_command.dart'; -import 'package:mockito/mockito.dart'; -import 'package:platform/platform.dart'; -import 'package:test/test.dart'; - -import 'common/package_command_test.mocks.dart'; -import 'mocks.dart'; -import 'util.dart'; - -void main() { - group('LicenseCheckCommand', () { - late CommandRunner runner; - late FileSystem fileSystem; - late Platform platform; - late Directory root; - - setUp(() { - fileSystem = MemoryFileSystem(); - platform = MockPlatformWithSeparator(); - final Directory packagesDir = - fileSystem.currentDirectory.childDirectory('packages'); - root = packagesDir.parent; - - final MockGitDir gitDir = MockGitDir(); - when(gitDir.path).thenReturn(packagesDir.parent.path); - - final LicenseCheckCommand command = LicenseCheckCommand( - packagesDir, - platform: platform, - gitDir: gitDir, - ); - runner = - CommandRunner('license_test', 'Test for $LicenseCheckCommand'); - runner.addCommand(command); - }); - - /// Writes a copyright+license block to [file], defaulting to a standard - /// block for this repository. - /// - /// [commentString] is added to the start of each line. - /// [prefix] is added to the start of the entire block. - /// [suffix] is added to the end of the entire block. - void writeLicense( - File file, { - String comment = '// ', - String prefix = '', - String suffix = '', - String copyright = - 'Copyright 2013 The Flutter Authors. All rights reserved.', - List license = const [ - 'Use of this source code is governed by a BSD-style license that can be', - 'found in the LICENSE file.', - ], - bool useCrlf = false, - }) { - final List lines = ['$prefix$comment$copyright']; - for (final String line in license) { - lines.add('$comment$line'); - } - final String newline = useCrlf ? '\r\n' : '\n'; - file.writeAsStringSync(lines.join(newline) + suffix + newline); - } - - test('looks at only expected extensions', () async { - final Map extensions = { - 'c': true, - 'cc': true, - 'cpp': true, - 'dart': true, - 'h': true, - 'html': true, - 'java': true, - 'json': false, - 'kt': true, - 'm': true, - 'md': false, - 'mm': true, - 'png': false, - 'swift': true, - 'sh': true, - 'yaml': false, - }; - - const String filenameBase = 'a_file'; - for (final String fileExtension in extensions.keys) { - root.childFile('$filenameBase.$fileExtension').createSync(); - } - - final List output = await runCapturingPrint( - runner, ['license-check'], errorHandler: (Error e) { - // Ignore failure; the files are empty so the check is expected to fail, - // but this test isn't for that behavior. - }); - - extensions.forEach((String fileExtension, bool shouldCheck) { - final Matcher logLineMatcher = - contains('Checking $filenameBase.$fileExtension'); - expect(output, shouldCheck ? logLineMatcher : isNot(logLineMatcher)); - }); - }); - - test('ignore list overrides extension matches', () async { - final List ignoredFiles = [ - // Ignored base names. - 'flutter_export_environment.sh', - 'GeneratedPluginRegistrant.java', - 'GeneratedPluginRegistrant.m', - 'generated_plugin_registrant.cc', - 'generated_plugin_registrant.cpp', - // Ignored path suffixes. - 'foo.g.dart', - 'foo.mocks.dart', - // Ignored files. - 'resource.h', - ]; - - for (final String name in ignoredFiles) { - root.childFile(name).createSync(); - } - - final List output = - await runCapturingPrint(runner, ['license-check']); - - for (final String name in ignoredFiles) { - expect(output, isNot(contains('Checking $name'))); - } - }); - - test('ignores submodules', () async { - const String submoduleName = 'a_submodule'; - - final File submoduleSpec = root.childFile('.gitmodules'); - submoduleSpec.writeAsStringSync(''' -[submodule "$submoduleName"] - path = $submoduleName - url = https://github.com/foo/$submoduleName -'''); - - const List submoduleFiles = [ - '$submoduleName/foo.dart', - '$submoduleName/a/b/bar.dart', - '$submoduleName/LICENSE', - ]; - for (final String filePath in submoduleFiles) { - root.childFile(filePath).createSync(recursive: true); - } - - final List output = - await runCapturingPrint(runner, ['license-check']); - - for (final String filePath in submoduleFiles) { - expect(output, isNot(contains('Checking $filePath'))); - } - }); - - test('passes if all checked files have license blocks', () async { - final File checked = root.childFile('checked.cc'); - checked.createSync(); - writeLicense(checked); - final File notChecked = root.childFile('not_checked.md'); - notChecked.createSync(); - - final List output = - await runCapturingPrint(runner, ['license-check']); - - // Sanity check that the test did actually check a file. - expect( - output, - containsAllInOrder([ - contains('Checking checked.cc'), - contains('All files passed validation!'), - ])); - }); - - test('passes correct license blocks on Windows', () async { - final File checked = root.childFile('checked.cc'); - checked.createSync(); - writeLicense(checked, useCrlf: true); - - final List output = - await runCapturingPrint(runner, ['license-check']); - - // Sanity check that the test did actually check a file. - expect( - output, - containsAllInOrder([ - contains('Checking checked.cc'), - contains('All files passed validation!'), - ])); - }); - - test('handles the comment styles for all supported languages', () async { - final File fileA = root.childFile('file_a.cc'); - fileA.createSync(); - writeLicense(fileA); - final File fileB = root.childFile('file_b.sh'); - fileB.createSync(); - writeLicense(fileB, comment: '# '); - final File fileC = root.childFile('file_c.html'); - fileC.createSync(); - writeLicense(fileC, comment: '', prefix: ''); - - final List output = - await runCapturingPrint(runner, ['license-check']); - - // Sanity check that the test did actually check the files. - expect( - output, - containsAllInOrder([ - contains('Checking file_a.cc'), - contains('Checking file_b.sh'), - contains('Checking file_c.html'), - contains('All files passed validation!'), - ])); - }); - - test('fails if any checked files are missing license blocks', () async { - final File goodA = root.childFile('good.cc'); - goodA.createSync(); - writeLicense(goodA); - final File goodB = root.childFile('good.h'); - goodB.createSync(); - writeLicense(goodB); - root.childFile('bad.cc').createSync(); - root.childFile('bad.h').createSync(); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['license-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - // Failure should give information about the problematic files. - expect( - output, - containsAllInOrder([ - contains( - 'The license block for these files is missing or incorrect:'), - contains(' bad.cc'), - contains(' bad.h'), - ])); - // Failure shouldn't print the success message. - expect(output, isNot(contains(contains('All files passed validation!')))); - }); - - test('fails if any checked files are missing just the copyright', () async { - final File good = root.childFile('good.cc'); - good.createSync(); - writeLicense(good); - final File bad = root.childFile('bad.cc'); - bad.createSync(); - writeLicense(bad, copyright: ''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['license-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - // Failure should give information about the problematic files. - expect( - output, - containsAllInOrder([ - contains( - 'The license block for these files is missing or incorrect:'), - contains(' bad.cc'), - ])); - // Failure shouldn't print the success message. - expect(output, isNot(contains(contains('All files passed validation!')))); - }); - - test('fails if any checked files are missing just the license', () async { - final File good = root.childFile('good.cc'); - good.createSync(); - writeLicense(good); - final File bad = root.childFile('bad.cc'); - bad.createSync(); - writeLicense(bad, license: []); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['license-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - // Failure should give information about the problematic files. - expect( - output, - containsAllInOrder([ - contains( - 'The license block for these files is missing or incorrect:'), - contains(' bad.cc'), - ])); - // Failure shouldn't print the success message. - expect(output, isNot(contains(contains('All files passed validation!')))); - }); - - test('fails if any third-party code is not in a third_party directory', - () async { - final File thirdPartyFile = root.childFile('third_party.cc'); - thirdPartyFile.createSync(); - writeLicense(thirdPartyFile, copyright: 'Copyright 2017 Someone Else'); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['license-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - // Failure should give information about the problematic files. - expect( - output, - containsAllInOrder([ - contains( - 'The license block for these files is missing or incorrect:'), - contains(' third_party.cc'), - ])); - // Failure shouldn't print the success message. - expect(output, isNot(contains(contains('All files passed validation!')))); - }); - - test('succeeds for third-party code in a third_party directory', () async { - final File thirdPartyFile = root - .childDirectory('a_plugin') - .childDirectory('lib') - .childDirectory('src') - .childDirectory('third_party') - .childFile('file.cc'); - thirdPartyFile.createSync(recursive: true); - writeLicense(thirdPartyFile, - copyright: 'Copyright 2017 Workiva Inc.', - license: [ - 'Licensed under the Apache License, Version 2.0 (the "License");', - 'you may not use this file except in compliance with the License.' - ]); - - final List output = - await runCapturingPrint(runner, ['license-check']); - - // Sanity check that the test did actually check the file. - expect( - output, - containsAllInOrder([ - contains('Checking a_plugin/lib/src/third_party/file.cc'), - contains('All files passed validation!'), - ])); - }); - - test('allows first-party code in a third_party directory', () async { - final File firstPartyFileInThirdParty = root - .childDirectory('a_plugin') - .childDirectory('lib') - .childDirectory('src') - .childDirectory('third_party') - .childFile('first_party.cc'); - firstPartyFileInThirdParty.createSync(recursive: true); - writeLicense(firstPartyFileInThirdParty); - - final List output = - await runCapturingPrint(runner, ['license-check']); - - // Sanity check that the test did actually check the file. - expect( - output, - containsAllInOrder([ - contains('Checking a_plugin/lib/src/third_party/first_party.cc'), - contains('All files passed validation!'), - ])); - }); - - test('fails for licenses that the tool does not expect', () async { - final File good = root.childFile('good.cc'); - good.createSync(); - writeLicense(good); - final File bad = root.childDirectory('third_party').childFile('bad.cc'); - bad.createSync(recursive: true); - writeLicense(bad, license: [ - 'This program is free software: you can redistribute it and/or modify', - 'it under the terms of the GNU General Public License', - ]); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['license-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - // Failure should give information about the problematic files. - expect( - output, - containsAllInOrder([ - contains( - 'No recognized license was found for the following third-party files:'), - contains(' third_party/bad.cc'), - ])); - // Failure shouldn't print the success message. - expect(output, isNot(contains(contains('All files passed validation!')))); - }); - - test('Apache is not recognized for new authors without validation changes', - () async { - final File good = root.childFile('good.cc'); - good.createSync(); - writeLicense(good); - final File bad = root.childDirectory('third_party').childFile('bad.cc'); - bad.createSync(recursive: true); - writeLicense( - bad, - copyright: 'Copyright 2017 Some New Authors.', - license: [ - 'Licensed under the Apache License, Version 2.0 (the "License");', - 'you may not use this file except in compliance with the License.' - ], - ); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['license-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - // Failure should give information about the problematic files. - expect( - output, - containsAllInOrder([ - contains( - 'No recognized license was found for the following third-party files:'), - contains(' third_party/bad.cc'), - ])); - // Failure shouldn't print the success message. - expect(output, isNot(contains(contains('All files passed validation!')))); - }); - - test('passes if all first-party LICENSE files are correctly formatted', - () async { - final File license = root.childFile('LICENSE'); - license.createSync(); - license.writeAsStringSync(_correctLicenseFileText); - - final List output = - await runCapturingPrint(runner, ['license-check']); - - // Sanity check that the test did actually check the file. - expect( - output, - containsAllInOrder([ - contains('Checking LICENSE'), - contains('All files passed validation!'), - ])); - }); - - test('passes correct LICENSE files on Windows', () async { - final File license = root.childFile('LICENSE'); - license.createSync(); - license - .writeAsStringSync(_correctLicenseFileText.replaceAll('\n', '\r\n')); - - final List output = - await runCapturingPrint(runner, ['license-check']); - - // Sanity check that the test did actually check the file. - expect( - output, - containsAllInOrder([ - contains('Checking LICENSE'), - contains('All files passed validation!'), - ])); - }); - - test('fails if any first-party LICENSE files are incorrectly formatted', - () async { - final File license = root.childFile('LICENSE'); - license.createSync(); - license.writeAsStringSync(_incorrectLicenseFileText); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['license-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect(output, isNot(contains(contains('All files passed validation!')))); - }); - - test('ignores third-party LICENSE format', () async { - final File license = - root.childDirectory('third_party').childFile('LICENSE'); - license.createSync(recursive: true); - license.writeAsStringSync(_incorrectLicenseFileText); - - final List output = - await runCapturingPrint(runner, ['license-check']); - - // The file shouldn't be checked. - expect(output, isNot(contains(contains('Checking third_party/LICENSE')))); - }); - - test('outputs all errors at the end', () async { - root.childFile('bad.cc').createSync(); - root - .childDirectory('third_party') - .childFile('bad.cc') - .createSync(recursive: true); - final File license = root.childFile('LICENSE'); - license.createSync(); - license.writeAsStringSync(_incorrectLicenseFileText); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['license-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Checking LICENSE'), - contains('Checking bad.cc'), - contains('Checking third_party/bad.cc'), - contains( - 'The following LICENSE files do not follow the expected format:'), - contains(' LICENSE'), - contains( - 'The license block for these files is missing or incorrect:'), - contains(' bad.cc'), - contains( - 'No recognized license was found for the following third-party files:'), - contains(' third_party/bad.cc'), - ])); - }); - }); -} - -class MockPlatformWithSeparator extends MockPlatform { - @override - String get pathSeparator => isWindows ? r'\' : '/'; -} - -const String _correctLicenseFileText = ''' -Copyright 2013 The Flutter Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - * Neither the name of Google Inc. nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -'''; - -// A common incorrect version created by copying text intended for a code file, -// with comment markers. -const String _incorrectLicenseFileText = ''' -// Copyright 2013 The Flutter Authors. All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -'''; diff --git a/script/tool/test/lint_android_command_test.dart b/script/tool/test/lint_android_command_test.dart deleted file mode 100644 index e4a6c5c859e4..000000000000 --- a/script/tool/test/lint_android_command_test.dart +++ /dev/null @@ -1,205 +0,0 @@ -// Copyright 2013 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. - -import 'dart:io' as io; - -import 'package:args/command_runner.dart'; -import 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:flutter_plugin_tools/src/common/core.dart'; -import 'package:flutter_plugin_tools/src/common/plugin_utils.dart'; -import 'package:flutter_plugin_tools/src/lint_android_command.dart'; -import 'package:test/test.dart'; - -import 'mocks.dart'; -import 'util.dart'; - -void main() { - group('LintAndroidCommand', () { - FileSystem fileSystem; - late Directory packagesDir; - late CommandRunner runner; - late MockPlatform mockPlatform; - late RecordingProcessRunner processRunner; - - setUp(() { - fileSystem = MemoryFileSystem(); - packagesDir = createPackagesDirectory(fileSystem: fileSystem); - mockPlatform = MockPlatform(); - processRunner = RecordingProcessRunner(); - final LintAndroidCommand command = LintAndroidCommand( - packagesDir, - processRunner: processRunner, - platform: mockPlatform, - ); - - runner = CommandRunner( - 'lint_android_test', 'Test for $LintAndroidCommand'); - runner.addCommand(command); - }); - - test('runs gradle lint', () async { - final RepositoryPackage plugin = - createFakePlugin('plugin1', packagesDir, extraFiles: [ - 'example/android/gradlew', - ], platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline) - }); - - final Directory androidDir = - plugin.getExamples().first.platformDirectory(FlutterPlatform.android); - - final List output = - await runCapturingPrint(runner, ['lint-android']); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - androidDir.childFile('gradlew').path, - const ['plugin1:lintDebug'], - androidDir.path, - ), - ]), - ); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin1'), - contains('No issues found!'), - ])); - }); - - test('runs on all examples', () async { - final List examples = ['example1', 'example2']; - final RepositoryPackage plugin = createFakePlugin('plugin1', packagesDir, - examples: examples, - extraFiles: [ - 'example/example1/android/gradlew', - 'example/example2/android/gradlew', - ], - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline) - }); - - final Iterable exampleAndroidDirs = plugin.getExamples().map( - (RepositoryPackage example) => - example.platformDirectory(FlutterPlatform.android)); - - final List output = - await runCapturingPrint(runner, ['lint-android']); - - expect( - processRunner.recordedCalls, - orderedEquals([ - for (final Directory directory in exampleAndroidDirs) - ProcessCall( - directory.childFile('gradlew').path, - const ['plugin1:lintDebug'], - directory.path, - ), - ]), - ); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin1'), - contains('No issues found!'), - ])); - }); - - test('fails if gradlew is missing', () async { - createFakePlugin('plugin1', packagesDir, - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline) - }); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['lint-android'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder( - [ - contains('Build examples before linting'), - ], - )); - }); - - test('fails if linting finds issues', () async { - final RepositoryPackage plugin = - createFakePlugin('plugin1', packagesDir, extraFiles: [ - 'example/android/gradlew', - ], platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline) - }); - - final String gradlewPath = plugin - .getExamples() - .first - .platformDirectory(FlutterPlatform.android) - .childFile('gradlew') - .path; - processRunner.mockProcessesForExecutable[gradlewPath] = [ - MockProcess(exitCode: 1), - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['lint-android'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder( - [ - contains('The following packages had errors:'), - ], - )); - }); - - test('skips non-Android plugins', () async { - createFakePlugin('plugin1', packagesDir); - - final List output = - await runCapturingPrint(runner, ['lint-android']); - - expect( - output, - containsAllInOrder( - [ - contains( - 'SKIPPING: Plugin does not have an Android implementation.') - ], - )); - }); - - test('skips non-inline plugins', () async { - createFakePlugin('plugin1', packagesDir, - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.federated) - }); - - final List output = - await runCapturingPrint(runner, ['lint-android']); - - expect( - output, - containsAllInOrder( - [ - contains( - 'SKIPPING: Plugin does not have an Android implementation.') - ], - )); - }); - }); -} diff --git a/script/tool/test/list_command_test.dart b/script/tool/test/list_command_test.dart deleted file mode 100644 index f19215c89b9e..000000000000 --- a/script/tool/test/list_command_test.dart +++ /dev/null @@ -1,197 +0,0 @@ -// Copyright 2013 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. - -import 'package:args/command_runner.dart'; -import 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:flutter_plugin_tools/src/list_command.dart'; -import 'package:test/test.dart'; - -import 'mocks.dart'; -import 'util.dart'; - -void main() { - group('ListCommand', () { - late FileSystem fileSystem; - late MockPlatform mockPlatform; - late Directory packagesDir; - late CommandRunner runner; - - setUp(() { - fileSystem = MemoryFileSystem(); - mockPlatform = MockPlatform(); - packagesDir = createPackagesDirectory(fileSystem: fileSystem); - final ListCommand command = - ListCommand(packagesDir, platform: mockPlatform); - - runner = CommandRunner('list_test', 'Test for $ListCommand'); - runner.addCommand(command); - }); - - test('lists top-level packages', () async { - createFakePackage('package1', packagesDir); - createFakePlugin('plugin2', packagesDir); - - final List plugins = - await runCapturingPrint(runner, ['list', '--type=package']); - - expect( - plugins, - orderedEquals([ - '/packages/package1', - '/packages/plugin2', - ]), - ); - }); - - test('lists examples', () async { - createFakePlugin('plugin1', packagesDir); - createFakePlugin('plugin2', packagesDir, - examples: ['example1', 'example2']); - createFakePlugin('plugin3', packagesDir, examples: []); - - final List examples = - await runCapturingPrint(runner, ['list', '--type=example']); - - expect( - examples, - orderedEquals([ - '/packages/plugin1/example', - '/packages/plugin2/example/example1', - '/packages/plugin2/example/example2', - ]), - ); - }); - - test('lists packages and subpackages', () async { - createFakePackage('package1', packagesDir); - createFakePlugin('plugin2', packagesDir, - examples: ['example1', 'example2']); - createFakePlugin('plugin3', packagesDir, examples: []); - - final List packages = await runCapturingPrint( - runner, ['list', '--type=package-or-subpackage']); - - expect( - packages, - unorderedEquals([ - '/packages/package1', - '/packages/package1/example', - '/packages/plugin2', - '/packages/plugin2/example/example1', - '/packages/plugin2/example/example2', - '/packages/plugin3', - ]), - ); - }); - - test('lists files', () async { - createFakePlugin('plugin1', packagesDir); - createFakePlugin('plugin2', packagesDir, - examples: ['example1', 'example2']); - createFakePlugin('plugin3', packagesDir, examples: []); - - final List examples = - await runCapturingPrint(runner, ['list', '--type=file']); - - expect( - examples, - unorderedEquals([ - '/packages/plugin1/pubspec.yaml', - '/packages/plugin1/AUTHORS', - '/packages/plugin1/CHANGELOG.md', - '/packages/plugin1/README.md', - '/packages/plugin1/example/pubspec.yaml', - '/packages/plugin2/pubspec.yaml', - '/packages/plugin2/AUTHORS', - '/packages/plugin2/CHANGELOG.md', - '/packages/plugin2/README.md', - '/packages/plugin2/example/example1/pubspec.yaml', - '/packages/plugin2/example/example2/pubspec.yaml', - '/packages/plugin3/pubspec.yaml', - '/packages/plugin3/AUTHORS', - '/packages/plugin3/CHANGELOG.md', - '/packages/plugin3/README.md', - ]), - ); - }); - - test('lists plugins using federated plugin layout', () async { - createFakePlugin('plugin1', packagesDir); - - // Create a federated plugin by creating a directory under the packages - // directory with several packages underneath. - final Directory federatedPluginDir = - packagesDir.childDirectory('my_plugin')..createSync(); - createFakePlugin('my_plugin', federatedPluginDir); - createFakePlugin('my_plugin_web', federatedPluginDir); - createFakePlugin('my_plugin_macos', federatedPluginDir); - - // Test without specifying `--type`. - final List plugins = - await runCapturingPrint(runner, ['list']); - - expect( - plugins, - unorderedEquals([ - '/packages/plugin1', - '/packages/my_plugin/my_plugin', - '/packages/my_plugin/my_plugin_web', - '/packages/my_plugin/my_plugin_macos', - ]), - ); - }); - - test('can filter plugins with the --packages argument', () async { - createFakePlugin('plugin1', packagesDir); - - // Create a federated plugin by creating a directory under the packages - // directory with several packages underneath. - final Directory federatedPluginDir = - packagesDir.childDirectory('my_plugin')..createSync(); - createFakePlugin('my_plugin', federatedPluginDir); - createFakePlugin('my_plugin_web', federatedPluginDir); - createFakePlugin('my_plugin_macos', federatedPluginDir); - - List plugins = await runCapturingPrint( - runner, ['list', '--packages=plugin1']); - expect( - plugins, - unorderedEquals([ - '/packages/plugin1', - ]), - ); - - plugins = await runCapturingPrint( - runner, ['list', '--packages=my_plugin']); - expect( - plugins, - unorderedEquals([ - '/packages/my_plugin/my_plugin', - '/packages/my_plugin/my_plugin_web', - '/packages/my_plugin/my_plugin_macos', - ]), - ); - - plugins = await runCapturingPrint( - runner, ['list', '--packages=my_plugin/my_plugin_web']); - expect( - plugins, - unorderedEquals([ - '/packages/my_plugin/my_plugin_web', - ]), - ); - - plugins = await runCapturingPrint(runner, - ['list', '--packages=my_plugin/my_plugin_web,plugin1']); - expect( - plugins, - unorderedEquals([ - '/packages/plugin1', - '/packages/my_plugin/my_plugin_web', - ]), - ); - }); - }); -} diff --git a/script/tool/test/make_deps_path_based_command_test.dart b/script/tool/test/make_deps_path_based_command_test.dart deleted file mode 100644 index e846a63fc68e..000000000000 --- a/script/tool/test/make_deps_path_based_command_test.dart +++ /dev/null @@ -1,483 +0,0 @@ -// Copyright 2013 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. - -import 'dart:io' as io; - -import 'package:args/command_runner.dart'; -import 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:flutter_plugin_tools/src/make_deps_path_based_command.dart'; -import 'package:mockito/mockito.dart'; -import 'package:test/test.dart'; - -import 'common/package_command_test.mocks.dart'; -import 'mocks.dart'; -import 'util.dart'; - -void main() { - FileSystem fileSystem; - late Directory packagesDir; - late CommandRunner runner; - late RecordingProcessRunner processRunner; - - setUp(() { - fileSystem = MemoryFileSystem(); - packagesDir = createPackagesDirectory(fileSystem: fileSystem); - - final MockGitDir gitDir = MockGitDir(); - when(gitDir.path).thenReturn(packagesDir.parent.path); - when(gitDir.runCommand(any, throwOnError: anyNamed('throwOnError'))) - .thenAnswer((Invocation invocation) { - final List arguments = - invocation.positionalArguments[0]! as List; - // Route git calls through the process runner, to make mock output - // consistent with other processes. Attach the first argument to the - // command to make targeting the mock results easier. - final String gitCommand = arguments.removeAt(0); - return processRunner.run('git-$gitCommand', arguments); - }); - - processRunner = RecordingProcessRunner(); - final MakeDepsPathBasedCommand command = - MakeDepsPathBasedCommand(packagesDir, gitDir: gitDir); - - runner = CommandRunner( - 'make-deps-path-based_command', 'Test for $MakeDepsPathBasedCommand'); - runner.addCommand(command); - }); - - /// Adds dummy 'dependencies:' entries for each package in [dependencies] - /// to [package]. - void addDependencies( - RepositoryPackage package, Iterable dependencies) { - final List lines = package.pubspecFile.readAsLinesSync(); - final int dependenciesStartIndex = lines.indexOf('dependencies:'); - assert(dependenciesStartIndex != -1); - lines.insertAll(dependenciesStartIndex + 1, [ - for (final String dependency in dependencies) ' $dependency: ^1.0.0', - ]); - package.pubspecFile.writeAsStringSync(lines.join('\n')); - } - - /// Adds a 'dev_dependencies:' section with entries for each package in - /// [dependencies] to [package]. - void addDevDependenciesSection( - RepositoryPackage package, Iterable devDependencies) { - final String originalContent = package.pubspecFile.readAsStringSync(); - package.pubspecFile.writeAsStringSync(''' -$originalContent - -dev_dependencies: -${devDependencies.map((String dep) => ' $dep: ^1.0.0').join('\n')} -'''); - } - - test('no-ops for no plugins', () async { - createFakePackage('foo', packagesDir, isFlutter: true); - final RepositoryPackage packageBar = - createFakePackage('bar', packagesDir, isFlutter: true); - addDependencies(packageBar, ['foo']); - final String originalPubspecContents = - packageBar.pubspecFile.readAsStringSync(); - - final List output = - await runCapturingPrint(runner, ['make-deps-path-based']); - - expect( - output, - containsAllInOrder([ - contains('No target dependencies'), - ]), - ); - // The 'foo' reference should not have been modified. - expect(packageBar.pubspecFile.readAsStringSync(), originalPubspecContents); - }); - - test('rewrites "dependencies" references', () async { - final RepositoryPackage simplePackage = - createFakePackage('foo', packagesDir, isFlutter: true); - final Directory pluginGroup = packagesDir.childDirectory('bar'); - - createFakePackage('bar_platform_interface', pluginGroup, isFlutter: true); - final RepositoryPackage pluginImplementation = - createFakePlugin('bar_android', pluginGroup); - final RepositoryPackage pluginAppFacing = - createFakePlugin('bar', pluginGroup); - - addDependencies(simplePackage, [ - 'bar', - 'bar_android', - 'bar_platform_interface', - ]); - addDependencies(pluginAppFacing, [ - 'bar_platform_interface', - 'bar_android', - ]); - addDependencies(pluginImplementation, [ - 'bar_platform_interface', - ]); - - final List output = await runCapturingPrint(runner, [ - 'make-deps-path-based', - '--target-dependencies=bar,bar_platform_interface' - ]); - - expect( - output, - containsAll([ - 'Rewriting references to: bar, bar_platform_interface...', - ' Modified packages/bar/bar/pubspec.yaml', - ' Modified packages/bar/bar_android/pubspec.yaml', - ' Modified packages/foo/pubspec.yaml', - ])); - expect( - output, - isNot(contains( - ' Modified packages/bar/bar_platform_interface/pubspec.yaml'))); - - expect( - simplePackage.pubspecFile.readAsLinesSync(), - containsAllInOrder([ - '# FOR TESTING ONLY. DO NOT MERGE.', - 'dependency_overrides:', - ' bar:', - ' path: ../bar/bar', - ' bar_platform_interface:', - ' path: ../bar/bar_platform_interface', - ])); - expect( - pluginAppFacing.pubspecFile.readAsLinesSync(), - containsAllInOrder([ - 'dependency_overrides:', - ' bar_platform_interface:', - ' path: ../../bar/bar_platform_interface', - ])); - }); - - test('rewrites "dev_dependencies" references', () async { - createFakePackage('foo', packagesDir); - final RepositoryPackage builderPackage = - createFakePackage('foo_builder', packagesDir); - - addDevDependenciesSection(builderPackage, [ - 'foo', - ]); - - final List output = await runCapturingPrint( - runner, ['make-deps-path-based', '--target-dependencies=foo']); - - expect( - output, - containsAll([ - 'Rewriting references to: foo...', - ' Modified packages/foo_builder/pubspec.yaml', - ])); - - expect( - builderPackage.pubspecFile.readAsLinesSync(), - containsAllInOrder([ - '# FOR TESTING ONLY. DO NOT MERGE.', - 'dependency_overrides:', - ' foo:', - ' path: ../foo', - ])); - }); - - test( - 'alphabetizes overrides from different sectinos to avoid lint warnings in analysis', - () async { - createFakePackage('a', packagesDir); - createFakePackage('b', packagesDir); - createFakePackage('c', packagesDir); - final RepositoryPackage targetPackage = - createFakePackage('target', packagesDir); - - addDependencies(targetPackage, ['a', 'c']); - addDevDependenciesSection(targetPackage, ['b']); - - final List output = await runCapturingPrint(runner, - ['make-deps-path-based', '--target-dependencies=c,a,b']); - - expect( - output, - containsAllInOrder([ - 'Rewriting references to: c, a, b...', - ' Modified packages/target/pubspec.yaml', - ])); - - expect( - targetPackage.pubspecFile.readAsLinesSync(), - containsAllInOrder([ - '# FOR TESTING ONLY. DO NOT MERGE.', - 'dependency_overrides:', - ' a:', - ' path: ../a', - ' b:', - ' path: ../b', - ' c:', - ' path: ../c', - ])); - }); - - // This test case ensures that running CI using this command on an interim - // PR that itself used this command won't fail on the rewrite step. - test('running a second time no-ops without failing', () async { - final RepositoryPackage simplePackage = - createFakePackage('foo', packagesDir, isFlutter: true); - final Directory pluginGroup = packagesDir.childDirectory('bar'); - - createFakePackage('bar_platform_interface', pluginGroup, isFlutter: true); - final RepositoryPackage pluginImplementation = - createFakePlugin('bar_android', pluginGroup); - final RepositoryPackage pluginAppFacing = - createFakePlugin('bar', pluginGroup); - - addDependencies(simplePackage, [ - 'bar', - 'bar_android', - 'bar_platform_interface', - ]); - addDependencies(pluginAppFacing, [ - 'bar_platform_interface', - 'bar_android', - ]); - addDependencies(pluginImplementation, [ - 'bar_platform_interface', - ]); - - await runCapturingPrint(runner, [ - 'make-deps-path-based', - '--target-dependencies=bar,bar_platform_interface' - ]); - final List output = await runCapturingPrint(runner, [ - 'make-deps-path-based', - '--target-dependencies=bar,bar_platform_interface' - ]); - - expect( - output, - containsAll([ - 'Rewriting references to: bar, bar_platform_interface...', - ' Skipped packages/bar/bar/pubspec.yaml - Already rewritten', - ' Skipped packages/bar/bar_android/pubspec.yaml - Already rewritten', - ' Skipped packages/foo/pubspec.yaml - Already rewritten', - ])); - }); - - group('target-dependencies-with-non-breaking-updates', () { - test('no-ops for no published changes', () async { - final RepositoryPackage package = createFakePackage('foo', packagesDir); - - final String changedFileOutput = [ - package.pubspecFile, - ].map((File file) => file.path).join('\n'); - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: changedFileOutput), - ]; - // Simulate no change to the version in the interface's pubspec.yaml. - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: package.pubspecFile.readAsStringSync()), - ]; - - final List output = await runCapturingPrint(runner, [ - 'make-deps-path-based', - '--target-dependencies-with-non-breaking-updates' - ]); - - expect( - output, - containsAllInOrder([ - contains('No target dependencies'), - ]), - ); - }); - - test('no-ops for no deleted packages', () async { - final String changedFileOutput = [ - // A change for a file that's not on disk simulates a deletion. - packagesDir.childDirectory('foo').childFile('pubspec.yaml'), - ].map((File file) => file.path).join('\n'); - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: changedFileOutput), - ]; - - final List output = await runCapturingPrint(runner, [ - 'make-deps-path-based', - '--target-dependencies-with-non-breaking-updates' - ]); - - expect( - output, - containsAllInOrder([ - contains('Skipping foo; deleted.'), - contains('No target dependencies'), - ]), - ); - }); - - test('includes bugfix version changes as targets', () async { - const String newVersion = '1.0.1'; - final RepositoryPackage package = - createFakePackage('foo', packagesDir, version: newVersion); - - final File pubspecFile = package.pubspecFile; - final String changedFileOutput = [ - pubspecFile, - ].map((File file) => file.path).join('\n'); - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: changedFileOutput), - ]; - final String gitPubspecContents = - pubspecFile.readAsStringSync().replaceAll(newVersion, '1.0.0'); - // Simulate no change to the version in the interface's pubspec.yaml. - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: gitPubspecContents), - ]; - - final List output = await runCapturingPrint(runner, [ - 'make-deps-path-based', - '--target-dependencies-with-non-breaking-updates' - ]); - - expect( - output, - containsAllInOrder([ - contains('Rewriting references to: foo...'), - ]), - ); - }); - - test('includes minor version changes to 1.0+ as targets', () async { - const String newVersion = '1.1.0'; - final RepositoryPackage package = - createFakePackage('foo', packagesDir, version: newVersion); - - final File pubspecFile = package.pubspecFile; - final String changedFileOutput = [ - pubspecFile, - ].map((File file) => file.path).join('\n'); - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: changedFileOutput), - ]; - final String gitPubspecContents = - pubspecFile.readAsStringSync().replaceAll(newVersion, '1.0.0'); - // Simulate no change to the version in the interface's pubspec.yaml. - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: gitPubspecContents), - ]; - - final List output = await runCapturingPrint(runner, [ - 'make-deps-path-based', - '--target-dependencies-with-non-breaking-updates' - ]); - - expect( - output, - containsAllInOrder([ - contains('Rewriting references to: foo...'), - ]), - ); - }); - - test('does not include major version changes as targets', () async { - const String newVersion = '2.0.0'; - final RepositoryPackage package = - createFakePackage('foo', packagesDir, version: newVersion); - - final File pubspecFile = package.pubspecFile; - final String changedFileOutput = [ - pubspecFile, - ].map((File file) => file.path).join('\n'); - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: changedFileOutput), - ]; - final String gitPubspecContents = - pubspecFile.readAsStringSync().replaceAll(newVersion, '1.0.0'); - // Simulate no change to the version in the interface's pubspec.yaml. - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: gitPubspecContents), - ]; - - final List output = await runCapturingPrint(runner, [ - 'make-deps-path-based', - '--target-dependencies-with-non-breaking-updates' - ]); - - expect( - output, - containsAllInOrder([ - contains('No target dependencies'), - ]), - ); - }); - - test('does not include minor version changes to 0.x as targets', () async { - const String newVersion = '0.8.0'; - final RepositoryPackage package = - createFakePackage('foo', packagesDir, version: newVersion); - - final File pubspecFile = package.pubspecFile; - final String changedFileOutput = [ - pubspecFile, - ].map((File file) => file.path).join('\n'); - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: changedFileOutput), - ]; - final String gitPubspecContents = - pubspecFile.readAsStringSync().replaceAll(newVersion, '0.7.0'); - // Simulate no change to the version in the interface's pubspec.yaml. - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: gitPubspecContents), - ]; - - final List output = await runCapturingPrint(runner, [ - 'make-deps-path-based', - '--target-dependencies-with-non-breaking-updates' - ]); - - expect( - output, - containsAllInOrder([ - contains('No target dependencies'), - ]), - ); - }); - - test('skips anything outside of the packages directory', () async { - final Directory toolDir = packagesDir.parent.childDirectory('tool'); - const String newVersion = '1.1.0'; - final RepositoryPackage package = createFakePackage( - 'flutter_plugin_tools', toolDir, - version: newVersion); - - // Simulate a minor version change so it would be a target. - final File pubspecFile = package.pubspecFile; - final String changedFileOutput = [ - pubspecFile, - ].map((File file) => file.path).join('\n'); - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: changedFileOutput), - ]; - final String gitPubspecContents = - pubspecFile.readAsStringSync().replaceAll(newVersion, '1.0.0'); - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: gitPubspecContents), - ]; - - final List output = await runCapturingPrint(runner, [ - 'make-deps-path-based', - '--target-dependencies-with-non-breaking-updates' - ]); - - expect( - output, - containsAllInOrder([ - contains( - 'Skipping /tool/flutter_plugin_tools/pubspec.yaml; not in packages directory.'), - contains('No target dependencies'), - ]), - ); - }); - }); -} diff --git a/script/tool/test/mocks.dart b/script/tool/test/mocks.dart deleted file mode 100644 index f6333ebd367d..000000000000 --- a/script/tool/test/mocks.dart +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright 2013 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. - -import 'dart:async'; -import 'dart:convert'; -import 'dart:io' as io; - -import 'package:file/file.dart'; -import 'package:mockito/mockito.dart'; -import 'package:platform/platform.dart'; - -class MockPlatform extends Mock implements Platform { - MockPlatform({ - this.isLinux = false, - this.isMacOS = false, - this.isWindows = false, - }); - - @override - bool isLinux; - - @override - bool isMacOS; - - @override - bool isWindows; - - @override - Uri get script => isWindows - ? Uri.file(r'C:\foo\bar', windows: true) - : Uri.file('/foo/bar', windows: false); - - @override - Map environment = {}; -} - -class MockProcess extends Mock implements io.Process { - /// Creates a mock process with the given results. - /// - /// The default encodings match the ProcessRunner defaults; mocks for - /// processes run with a different encoding will need to be created with - /// the matching encoding. - MockProcess({ - int exitCode = 0, - String? stdout, - String? stderr, - Encoding stdoutEncoding = io.systemEncoding, - Encoding stderrEncoding = io.systemEncoding, - }) : _exitCode = exitCode { - if (stdout != null) { - _stdoutController.add(stdoutEncoding.encoder.convert(stdout)); - } - if (stderr != null) { - _stderrController.add(stderrEncoding.encoder.convert(stderr)); - } - _stdoutController.close(); - _stderrController.close(); - } - - final int _exitCode; - final StreamController> _stdoutController = - StreamController>(); - final StreamController> _stderrController = - StreamController>(); - final MockIOSink stdinMock = MockIOSink(); - - @override - int get pid => 99; - - @override - Future get exitCode async => _exitCode; - - @override - Stream> get stdout => _stdoutController.stream; - - @override - Stream> get stderr => _stderrController.stream; - - @override - IOSink get stdin => stdinMock; -} - -class MockIOSink extends Mock implements IOSink { - List lines = []; - - @override - void writeln([Object? obj = '']) => lines.add(obj.toString()); -} diff --git a/script/tool/test/native_test_command_test.dart b/script/tool/test/native_test_command_test.dart deleted file mode 100644 index f24d014bbfea..000000000000 --- a/script/tool/test/native_test_command_test.dart +++ /dev/null @@ -1,1796 +0,0 @@ -// Copyright 2013 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. - -import 'dart:convert'; -import 'dart:io' as io; - -import 'package:args/command_runner.dart'; -import 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:flutter_plugin_tools/src/common/cmake.dart'; -import 'package:flutter_plugin_tools/src/common/core.dart'; -import 'package:flutter_plugin_tools/src/common/file_utils.dart'; -import 'package:flutter_plugin_tools/src/common/plugin_utils.dart'; -import 'package:flutter_plugin_tools/src/native_test_command.dart'; -import 'package:platform/platform.dart'; -import 'package:test/test.dart'; - -import 'mocks.dart'; -import 'util.dart'; - -const String _androidIntegrationTestFilter = - '-Pandroid.testInstrumentationRunnerArguments.' - 'notAnnotation=io.flutter.plugins.DartIntegrationTest'; - -final Map _kDeviceListMap = { - 'runtimes': >[ - { - 'bundlePath': - '/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 13.4.simruntime', - 'buildversion': '17L255', - 'runtimeRoot': - '/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 13.4.simruntime/Contents/Resources/RuntimeRoot', - 'identifier': 'com.apple.CoreSimulator.SimRuntime.iOS-13-4', - 'version': '13.4', - 'isAvailable': true, - 'name': 'iOS 13.4' - }, - ], - 'devices': { - 'com.apple.CoreSimulator.SimRuntime.iOS-13-4': >[ - { - 'dataPath': - '/Users/xxx/Library/Developer/CoreSimulator/Devices/1E76A0FD-38AC-4537-A989-EA639D7D012A/data', - 'logPath': - '/Users/xxx/Library/Logs/CoreSimulator/1E76A0FD-38AC-4537-A989-EA639D7D012A', - 'udid': '1E76A0FD-38AC-4537-A989-EA639D7D012A', - 'isAvailable': true, - 'deviceTypeIdentifier': - 'com.apple.CoreSimulator.SimDeviceType.iPhone-8-Plus', - 'state': 'Shutdown', - 'name': 'iPhone 8 Plus' - } - ] - } -}; - -const String _fakeCmakeCommand = 'path/to/cmake'; - -void _createFakeCMakeCache(RepositoryPackage plugin, Platform platform) { - final CMakeProject project = CMakeProject(getExampleDir(plugin), - platform: platform, buildMode: 'Release'); - final File cache = project.buildDirectory.childFile('CMakeCache.txt'); - cache.createSync(recursive: true); - cache.writeAsStringSync('CMAKE_COMMAND:INTERNAL=$_fakeCmakeCommand'); -} - -// TODO(stuartmorgan): Rework these tests to use a mock Xcode instead of -// doing all the process mocking and validation. -void main() { - const String kDestination = '--ios-destination'; - - group('test native_test_command on Posix', () { - late FileSystem fileSystem; - late MockPlatform mockPlatform; - late Directory packagesDir; - late CommandRunner runner; - late RecordingProcessRunner processRunner; - - setUp(() { - fileSystem = MemoryFileSystem(); - // iOS and macOS tests expect macOS, Linux tests expect Linux; nothing - // needs to distinguish between Linux and macOS, so set both to true to - // allow them to share a setup group. - mockPlatform = MockPlatform(isMacOS: true, isLinux: true); - packagesDir = createPackagesDirectory(fileSystem: fileSystem); - processRunner = RecordingProcessRunner(); - final NativeTestCommand command = NativeTestCommand(packagesDir, - processRunner: processRunner, platform: mockPlatform); - - runner = CommandRunner( - 'native_test_command', 'Test for native_test_command'); - runner.addCommand(command); - }); - - // Returns a MockProcess to provide for "xcrun xcodebuild -list" for a - // project that contains [targets]. - MockProcess getMockXcodebuildListProcess(List targets) { - final Map projects = { - 'project': { - 'targets': targets, - } - }; - return MockProcess(stdout: jsonEncode(projects)); - } - - // Returns the ProcessCall to expect for checking the targets present in - // the [package]'s [platform]/Runner.xcodeproj. - ProcessCall getTargetCheckCall(Directory package, String platform) { - return ProcessCall( - 'xcrun', - [ - 'xcodebuild', - '-list', - '-json', - '-project', - package - .childDirectory(platform) - .childDirectory('Runner.xcodeproj') - .path, - ], - null); - } - - // Returns the ProcessCall to expect for running the tests in the - // workspace [platform]/Runner.xcworkspace, with the given extra flags. - ProcessCall getRunTestCall( - Directory package, - String platform, { - String? destination, - List extraFlags = const [], - }) { - return ProcessCall( - 'xcrun', - [ - 'xcodebuild', - 'test', - '-workspace', - '$platform/Runner.xcworkspace', - '-scheme', - 'Runner', - '-configuration', - 'Debug', - if (destination != null) ...['-destination', destination], - ...extraFlags, - 'GCC_TREAT_WARNINGS_AS_ERRORS=YES', - ], - package.path); - } - - // Returns the ProcessCall to expect for build the Linux unit tests for the - // given plugin. - ProcessCall getLinuxBuildCall(RepositoryPackage plugin) { - return ProcessCall( - 'cmake', - [ - '--build', - getExampleDir(plugin) - .childDirectory('build') - .childDirectory('linux') - .childDirectory('x64') - .childDirectory('release') - .path, - '--target', - 'unit_tests' - ], - null); - } - - test('fails if no platforms are provided', () async { - Error? commandError; - final List output = await runCapturingPrint( - runner, ['native-test'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('At least one platform flag must be provided.'), - ]), - ); - }); - - test('fails if all test types are disabled', () async { - Error? commandError; - final List output = await runCapturingPrint(runner, [ - 'native-test', - '--macos', - '--no-unit', - '--no-integration', - ], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('At least one test type must be enabled.'), - ]), - ); - }); - - test('reports skips with no tests', () async { - final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir, - platformSupport: { - platformMacOS: const PlatformDetails(PlatformSupport.inline), - }); - - final Directory pluginExampleDirectory = getExampleDir(plugin); - - processRunner.mockProcessesForExecutable['xcrun'] = [ - getMockXcodebuildListProcess(['RunnerTests', 'RunnerUITests']), - // Exit code 66 from testing indicates no tests. - MockProcess(exitCode: 66), - ]; - final List output = await runCapturingPrint( - runner, ['native-test', '--macos', '--no-unit']); - - expect( - output, - containsAllInOrder([ - contains('No tests found.'), - contains('Skipped 1 package(s)'), - ])); - - expect( - processRunner.recordedCalls, - orderedEquals([ - getTargetCheckCall(pluginExampleDirectory, 'macos'), - getRunTestCall(pluginExampleDirectory, 'macos', - extraFlags: ['-only-testing:RunnerUITests']), - ])); - }); - - group('iOS', () { - test('skip if iOS is not supported', () async { - createFakePlugin('plugin', packagesDir, - platformSupport: { - platformMacOS: const PlatformDetails(PlatformSupport.inline), - }); - - final List output = await runCapturingPrint(runner, - ['native-test', '--ios', kDestination, 'foo_destination']); - expect( - output, - containsAllInOrder([ - contains('No implementation for iOS.'), - contains('SKIPPING: Nothing to test for target platform(s).'), - ])); - expect(processRunner.recordedCalls, orderedEquals([])); - }); - - test('skip if iOS is implemented in a federated package', () async { - createFakePlugin('plugin', packagesDir, - platformSupport: { - platformIOS: const PlatformDetails(PlatformSupport.federated) - }); - - final List output = await runCapturingPrint(runner, - ['native-test', '--ios', kDestination, 'foo_destination']); - expect( - output, - containsAllInOrder([ - contains('No implementation for iOS.'), - contains('SKIPPING: Nothing to test for target platform(s).'), - ])); - expect(processRunner.recordedCalls, orderedEquals([])); - }); - - test('running with correct destination', () async { - final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir, - platformSupport: { - platformIOS: const PlatformDetails(PlatformSupport.inline) - }); - - final Directory pluginExampleDirectory = getExampleDir(plugin); - - processRunner.mockProcessesForExecutable['xcrun'] = [ - getMockXcodebuildListProcess( - ['RunnerTests', 'RunnerUITests']), - ]; - - final List output = await runCapturingPrint(runner, [ - 'native-test', - '--ios', - kDestination, - 'foo_destination', - ]); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('Successfully ran iOS xctest for plugin/example') - ])); - - expect( - processRunner.recordedCalls, - orderedEquals([ - getTargetCheckCall(pluginExampleDirectory, 'ios'), - getRunTestCall(pluginExampleDirectory, 'ios', - destination: 'foo_destination'), - ])); - }); - - test('Not specifying --ios-destination assigns an available simulator', - () async { - final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir, - platformSupport: { - platformIOS: const PlatformDetails(PlatformSupport.inline) - }); - final Directory pluginExampleDirectory = getExampleDir(plugin); - - processRunner.mockProcessesForExecutable['xcrun'] = [ - MockProcess(stdout: jsonEncode(_kDeviceListMap)), // simctl - getMockXcodebuildListProcess( - ['RunnerTests', 'RunnerUITests']), - ]; - - await runCapturingPrint(runner, ['native-test', '--ios']); - - expect( - processRunner.recordedCalls, - orderedEquals([ - const ProcessCall( - 'xcrun', - [ - 'simctl', - 'list', - 'devices', - 'runtimes', - 'available', - '--json', - ], - null), - getTargetCheckCall(pluginExampleDirectory, 'ios'), - getRunTestCall(pluginExampleDirectory, 'ios', - destination: 'id=1E76A0FD-38AC-4537-A989-EA639D7D012A'), - ])); - }); - }); - - group('macOS', () { - test('skip if macOS is not supported', () async { - createFakePlugin('plugin', packagesDir); - - final List output = - await runCapturingPrint(runner, ['native-test', '--macos']); - - expect( - output, - containsAllInOrder([ - contains('No implementation for macOS.'), - contains('SKIPPING: Nothing to test for target platform(s).'), - ])); - expect(processRunner.recordedCalls, orderedEquals([])); - }); - - test('skip if macOS is implemented in a federated package', () async { - createFakePlugin('plugin', packagesDir, - platformSupport: { - platformMacOS: const PlatformDetails(PlatformSupport.federated), - }); - - final List output = - await runCapturingPrint(runner, ['native-test', '--macos']); - - expect( - output, - containsAllInOrder([ - contains('No implementation for macOS.'), - contains('SKIPPING: Nothing to test for target platform(s).'), - ])); - expect(processRunner.recordedCalls, orderedEquals([])); - }); - - test('runs for macOS plugin', () async { - final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir, - platformSupport: { - platformMacOS: const PlatformDetails(PlatformSupport.inline), - }); - - final Directory pluginExampleDirectory = getExampleDir(plugin); - - processRunner.mockProcessesForExecutable['xcrun'] = [ - getMockXcodebuildListProcess( - ['RunnerTests', 'RunnerUITests']), - ]; - - final List output = await runCapturingPrint(runner, [ - 'native-test', - '--macos', - ]); - - expect( - output, - contains( - contains('Successfully ran macOS xctest for plugin/example'))); - - expect( - processRunner.recordedCalls, - orderedEquals([ - getTargetCheckCall(pluginExampleDirectory, 'macos'), - getRunTestCall(pluginExampleDirectory, 'macos'), - ])); - }); - }); - - group('Android', () { - test('runs Java unit tests in Android implementation folder', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', - packagesDir, - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline) - }, - extraFiles: [ - 'example/android/gradlew', - 'android/src/test/example_test.java', - ], - ); - - await runCapturingPrint(runner, ['native-test', '--android']); - - final Directory androidFolder = plugin - .getExamples() - .first - .platformDirectory(FlutterPlatform.android); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - androidFolder.childFile('gradlew').path, - const ['testDebugUnitTest'], - androidFolder.path, - ), - ]), - ); - }); - - test('runs Java unit tests in example folder', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', - packagesDir, - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline) - }, - extraFiles: [ - 'example/android/gradlew', - 'example/android/app/src/test/example_test.java', - ], - ); - - await runCapturingPrint(runner, ['native-test', '--android']); - - final Directory androidFolder = plugin - .getExamples() - .first - .platformDirectory(FlutterPlatform.android); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - androidFolder.childFile('gradlew').path, - const ['testDebugUnitTest'], - androidFolder.path, - ), - ]), - ); - }); - - test('runs Java integration tests', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', - packagesDir, - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline) - }, - extraFiles: [ - 'example/android/gradlew', - 'example/android/app/src/androidTest/IntegrationTest.java', - ], - ); - - await runCapturingPrint( - runner, ['native-test', '--android', '--no-unit']); - - final Directory androidFolder = plugin - .getExamples() - .first - .platformDirectory(FlutterPlatform.android); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - androidFolder.childFile('gradlew').path, - const [ - 'app:connectedAndroidTest', - _androidIntegrationTestFilter, - ], - androidFolder.path, - ), - ]), - ); - }); - - test( - 'ignores Java integration test files associated with integration_test', - () async { - createFakePlugin( - 'plugin', - packagesDir, - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline) - }, - extraFiles: [ - 'example/android/gradlew', - 'example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java', - 'example/android/app/src/androidTest/java/io/flutter/plugins/plugin/FlutterActivityTest.java', - 'example/android/app/src/androidTest/java/io/flutter/plugins/plugin/MainActivityTest.java', - ], - ); - - await runCapturingPrint( - runner, ['native-test', '--android', '--no-unit']); - - // Nothing should run since those files are all - // integration_test-specific. - expect( - processRunner.recordedCalls, - orderedEquals([]), - ); - }); - - test('runs all tests when present', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', - packagesDir, - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline) - }, - extraFiles: [ - 'android/src/test/example_test.java', - 'example/android/gradlew', - 'example/android/app/src/androidTest/IntegrationTest.java', - ], - ); - - await runCapturingPrint(runner, ['native-test', '--android']); - - final Directory androidFolder = plugin - .getExamples() - .first - .platformDirectory(FlutterPlatform.android); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - androidFolder.childFile('gradlew').path, - const ['testDebugUnitTest'], - androidFolder.path, - ), - ProcessCall( - androidFolder.childFile('gradlew').path, - const [ - 'app:connectedAndroidTest', - _androidIntegrationTestFilter, - ], - androidFolder.path, - ), - ]), - ); - }); - - test('honors --no-unit', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', - packagesDir, - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline) - }, - extraFiles: [ - 'android/src/test/example_test.java', - 'example/android/gradlew', - 'example/android/app/src/androidTest/IntegrationTest.java', - ], - ); - - await runCapturingPrint( - runner, ['native-test', '--android', '--no-unit']); - - final Directory androidFolder = plugin - .getExamples() - .first - .platformDirectory(FlutterPlatform.android); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - androidFolder.childFile('gradlew').path, - const [ - 'app:connectedAndroidTest', - _androidIntegrationTestFilter, - ], - androidFolder.path, - ), - ]), - ); - }); - - test('honors --no-integration', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', - packagesDir, - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline) - }, - extraFiles: [ - 'android/src/test/example_test.java', - 'example/android/gradlew', - 'example/android/app/src/androidTest/IntegrationTest.java', - ], - ); - - await runCapturingPrint( - runner, ['native-test', '--android', '--no-integration']); - - final Directory androidFolder = plugin - .getExamples() - .first - .platformDirectory(FlutterPlatform.android); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - androidFolder.childFile('gradlew').path, - const ['testDebugUnitTest'], - androidFolder.path, - ), - ]), - ); - }); - - test('fails when the app needs to be built', () async { - createFakePlugin( - 'plugin', - packagesDir, - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline) - }, - extraFiles: [ - 'example/android/app/src/test/example_test.java', - ], - ); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['native-test', '--android'], - errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - - expect( - output, - containsAllInOrder([ - contains('ERROR: Run "flutter build apk" on plugin/example'), - contains('plugin:\n' - ' Examples must be built before testing.') - ]), - ); - }); - - test('logs missing test types', () async { - // No unit tests. - createFakePlugin( - 'plugin1', - packagesDir, - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline) - }, - extraFiles: [ - 'example/android/gradlew', - 'example/android/app/src/androidTest/IntegrationTest.java', - ], - ); - // No integration tests. - createFakePlugin( - 'plugin2', - packagesDir, - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline) - }, - extraFiles: [ - 'android/src/test/example_test.java', - 'example/android/gradlew', - ], - ); - - final List output = await runCapturingPrint( - runner, ['native-test', '--android'], - errorHandler: (Error e) { - // Having no unit tests is fatal, but that's not the point of this - // test so just ignore the failure. - }); - - expect( - output, - containsAllInOrder([ - contains('No Android unit tests found for plugin1/example'), - contains('Running integration tests...'), - contains( - 'No Android integration tests found for plugin2/example'), - contains('Running unit tests...'), - ])); - }); - - test('fails when a unit test fails', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', - packagesDir, - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline) - }, - extraFiles: [ - 'example/android/gradlew', - 'example/android/app/src/test/example_test.java', - ], - ); - - final String gradlewPath = plugin - .getExamples() - .first - .platformDirectory(FlutterPlatform.android) - .childFile('gradlew') - .path; - processRunner.mockProcessesForExecutable[gradlewPath] = [ - MockProcess(exitCode: 1) - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['native-test', '--android'], - errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - - expect( - output, - containsAllInOrder([ - contains('plugin/example unit tests failed.'), - contains('The following packages had errors:'), - contains('plugin') - ]), - ); - }); - - test('fails when an integration test fails', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', - packagesDir, - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline) - }, - extraFiles: [ - 'example/android/gradlew', - 'example/android/app/src/test/example_test.java', - 'example/android/app/src/androidTest/IntegrationTest.java', - ], - ); - - final String gradlewPath = plugin - .getExamples() - .first - .platformDirectory(FlutterPlatform.android) - .childFile('gradlew') - .path; - processRunner.mockProcessesForExecutable[gradlewPath] = [ - MockProcess(), // unit passes - MockProcess(exitCode: 1), // integration fails - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['native-test', '--android'], - errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - - expect( - output, - containsAllInOrder([ - contains('plugin/example integration tests failed.'), - contains('The following packages had errors:'), - contains('plugin') - ]), - ); - }); - - test('fails if there are no unit tests', () async { - createFakePlugin( - 'plugin', - packagesDir, - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline) - }, - extraFiles: [ - 'example/android/gradlew', - 'example/android/app/src/androidTest/IntegrationTest.java', - ], - ); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['native-test', '--android'], - errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - - expect( - output, - containsAllInOrder([ - contains('No Android unit tests found for plugin/example'), - contains( - 'No unit tests ran. Plugins are required to have unit tests.'), - contains('The following packages had errors:'), - contains('plugin:\n' - ' No unit tests ran (use --exclude if this is intentional).') - ]), - ); - }); - - test('skips if Android is not supported', () async { - createFakePlugin( - 'plugin', - packagesDir, - ); - - final List output = await runCapturingPrint( - runner, ['native-test', '--android']); - - expect( - output, - containsAllInOrder([ - contains('No implementation for Android.'), - contains('SKIPPING: Nothing to test for target platform(s).'), - ]), - ); - }); - - test('skips when running no tests in integration-only mode', () async { - createFakePlugin( - 'plugin', - packagesDir, - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline) - }, - ); - - final List output = await runCapturingPrint( - runner, ['native-test', '--android', '--no-unit']); - - expect( - output, - containsAllInOrder([ - contains('No Android integration tests found for plugin/example'), - contains('SKIPPING: No tests found.'), - ]), - ); - }); - }); - - group('Linux', () { - test('builds and runs unit tests', () async { - const String testBinaryRelativePath = - 'build/linux/x64/release/bar/plugin_test'; - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, extraFiles: [ - 'example/$testBinaryRelativePath' - ], platformSupport: { - platformLinux: const PlatformDetails(PlatformSupport.inline), - }); - _createFakeCMakeCache(plugin, mockPlatform); - - final File testBinary = childFileWithSubcomponents(plugin.directory, - ['example', ...testBinaryRelativePath.split('/')]); - - final List output = await runCapturingPrint(runner, [ - 'native-test', - '--linux', - '--no-integration', - ]); - - expect( - output, - containsAllInOrder([ - contains('Running plugin_test...'), - contains('No issues found!'), - ]), - ); - - expect( - processRunner.recordedCalls, - orderedEquals([ - getLinuxBuildCall(plugin), - ProcessCall(testBinary.path, const [], null), - ])); - }); - - test('only runs release unit tests', () async { - const String debugTestBinaryRelativePath = - 'build/linux/x64/debug/bar/plugin_test'; - const String releaseTestBinaryRelativePath = - 'build/linux/x64/release/bar/plugin_test'; - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, extraFiles: [ - 'example/$debugTestBinaryRelativePath', - 'example/$releaseTestBinaryRelativePath' - ], platformSupport: { - platformLinux: const PlatformDetails(PlatformSupport.inline), - }); - _createFakeCMakeCache(plugin, mockPlatform); - - final File releaseTestBinary = childFileWithSubcomponents( - plugin.directory, - ['example', ...releaseTestBinaryRelativePath.split('/')]); - - final List output = await runCapturingPrint(runner, [ - 'native-test', - '--linux', - '--no-integration', - ]); - - expect( - output, - containsAllInOrder([ - contains('Running plugin_test...'), - contains('No issues found!'), - ]), - ); - - expect( - processRunner.recordedCalls, - orderedEquals([ - getLinuxBuildCall(plugin), - ProcessCall(releaseTestBinary.path, const [], null), - ])); - }); - - test('fails if CMake has not been configured', () async { - createFakePlugin('plugin', packagesDir, - platformSupport: { - platformLinux: const PlatformDetails(PlatformSupport.inline), - }); - - Error? commandError; - final List output = await runCapturingPrint(runner, [ - 'native-test', - '--linux', - '--no-integration', - ], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('plugin:\n' - ' Examples must be built before testing.') - ]), - ); - - expect(processRunner.recordedCalls, orderedEquals([])); - }); - - test('fails if there are no unit tests', () async { - final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir, - platformSupport: { - platformLinux: const PlatformDetails(PlatformSupport.inline), - }); - _createFakeCMakeCache(plugin, mockPlatform); - - Error? commandError; - final List output = await runCapturingPrint(runner, [ - 'native-test', - '--linux', - '--no-integration', - ], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('No test binaries found.'), - ]), - ); - - expect( - processRunner.recordedCalls, - orderedEquals([ - getLinuxBuildCall(plugin), - ])); - }); - - test('fails if a unit test fails', () async { - const String testBinaryRelativePath = - 'build/linux/x64/release/bar/plugin_test'; - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, extraFiles: [ - 'example/$testBinaryRelativePath' - ], platformSupport: { - platformLinux: const PlatformDetails(PlatformSupport.inline), - }); - _createFakeCMakeCache(plugin, mockPlatform); - - final File testBinary = childFileWithSubcomponents(plugin.directory, - ['example', ...testBinaryRelativePath.split('/')]); - - processRunner.mockProcessesForExecutable[testBinary.path] = - [MockProcess(exitCode: 1)]; - - Error? commandError; - final List output = await runCapturingPrint(runner, [ - 'native-test', - '--linux', - '--no-integration', - ], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Running plugin_test...'), - ]), - ); - - expect( - processRunner.recordedCalls, - orderedEquals([ - getLinuxBuildCall(plugin), - ProcessCall(testBinary.path, const [], null), - ])); - }); - }); - - // Tests behaviors of implementation that is shared between iOS and macOS. - group('iOS/macOS', () { - test('fails if xcrun fails', () async { - createFakePlugin('plugin', packagesDir, - platformSupport: { - platformMacOS: const PlatformDetails(PlatformSupport.inline), - }); - - processRunner.mockProcessesForExecutable['xcrun'] = [ - MockProcess(exitCode: 1) - ]; - - Error? commandError; - final List output = - await runCapturingPrint(runner, ['native-test', '--macos'], - errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('The following packages had errors:'), - contains(' plugin'), - ]), - ); - }); - - test('honors unit-only', () async { - final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir, - platformSupport: { - platformMacOS: const PlatformDetails(PlatformSupport.inline), - }); - - final Directory pluginExampleDirectory = getExampleDir(plugin); - - processRunner.mockProcessesForExecutable['xcrun'] = [ - getMockXcodebuildListProcess( - ['RunnerTests', 'RunnerUITests']), - ]; - - final List output = await runCapturingPrint(runner, [ - 'native-test', - '--macos', - '--no-integration', - ]); - - expect( - output, - contains( - contains('Successfully ran macOS xctest for plugin/example'))); - - // --no-integration should translate to '-only-testing:RunnerTests'. - expect( - processRunner.recordedCalls, - orderedEquals([ - getTargetCheckCall(pluginExampleDirectory, 'macos'), - getRunTestCall(pluginExampleDirectory, 'macos', - extraFlags: ['-only-testing:RunnerTests']), - ])); - }); - - test('honors integration-only', () async { - final RepositoryPackage plugin1 = createFakePlugin( - 'plugin', packagesDir, - platformSupport: { - platformMacOS: const PlatformDetails(PlatformSupport.inline), - }); - - final Directory pluginExampleDirectory = getExampleDir(plugin1); - - processRunner.mockProcessesForExecutable['xcrun'] = [ - getMockXcodebuildListProcess( - ['RunnerTests', 'RunnerUITests']), - ]; - - final List output = await runCapturingPrint(runner, [ - 'native-test', - '--macos', - '--no-unit', - ]); - - expect( - output, - contains( - contains('Successfully ran macOS xctest for plugin/example'))); - - // --no-unit should translate to '-only-testing:RunnerUITests'. - expect( - processRunner.recordedCalls, - orderedEquals([ - getTargetCheckCall(pluginExampleDirectory, 'macos'), - getRunTestCall(pluginExampleDirectory, 'macos', - extraFlags: ['-only-testing:RunnerUITests']), - ])); - }); - - test('skips when the requested target is not present', () async { - final RepositoryPackage plugin1 = createFakePlugin( - 'plugin', packagesDir, - platformSupport: { - platformMacOS: const PlatformDetails(PlatformSupport.inline), - }); - - final Directory pluginExampleDirectory = getExampleDir(plugin1); - - // Simulate a project with unit tests but no integration tests... - processRunner.mockProcessesForExecutable['xcrun'] = [ - getMockXcodebuildListProcess(['RunnerTests']), - ]; - - // ... then try to run only integration tests. - final List output = await runCapturingPrint(runner, [ - 'native-test', - '--macos', - '--no-unit', - ]); - - expect( - output, - containsAllInOrder([ - contains( - 'No "RunnerUITests" target in plugin/example; skipping.'), - ])); - - expect( - processRunner.recordedCalls, - orderedEquals([ - getTargetCheckCall(pluginExampleDirectory, 'macos'), - ])); - }); - - test('fails if there are no unit tests', () async { - final RepositoryPackage plugin1 = createFakePlugin( - 'plugin', packagesDir, - platformSupport: { - platformMacOS: const PlatformDetails(PlatformSupport.inline), - }); - - final Directory pluginExampleDirectory = getExampleDir(plugin1); - - processRunner.mockProcessesForExecutable['xcrun'] = [ - getMockXcodebuildListProcess(['RunnerUITests']), - ]; - - Error? commandError; - final List output = - await runCapturingPrint(runner, ['native-test', '--macos'], - errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('No "RunnerTests" target in plugin/example; skipping.'), - contains( - 'No unit tests ran. Plugins are required to have unit tests.'), - contains('The following packages had errors:'), - contains('plugin:\n' - ' No unit tests ran (use --exclude if this is intentional).'), - ])); - - expect( - processRunner.recordedCalls, - orderedEquals([ - getTargetCheckCall(pluginExampleDirectory, 'macos'), - ])); - }); - - test('fails if unable to check for requested target', () async { - final RepositoryPackage plugin1 = createFakePlugin( - 'plugin', packagesDir, - platformSupport: { - platformMacOS: const PlatformDetails(PlatformSupport.inline), - }); - - final Directory pluginExampleDirectory = getExampleDir(plugin1); - - processRunner.mockProcessesForExecutable['xcrun'] = [ - MockProcess(exitCode: 1), // xcodebuild -list - ]; - - Error? commandError; - final List output = await runCapturingPrint(runner, [ - 'native-test', - '--macos', - '--no-integration', - ], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Unable to check targets for plugin/example.'), - ]), - ); - - expect( - processRunner.recordedCalls, - orderedEquals([ - getTargetCheckCall(pluginExampleDirectory, 'macos'), - ])); - }); - }); - - group('multiplatform', () { - test('runs all platfroms when supported', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', - packagesDir, - extraFiles: [ - 'example/android/gradlew', - 'android/src/test/example_test.java', - ], - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline), - platformIOS: const PlatformDetails(PlatformSupport.inline), - platformMacOS: const PlatformDetails(PlatformSupport.inline), - }, - ); - - final Directory pluginExampleDirectory = getExampleDir(plugin); - final Directory androidFolder = - pluginExampleDirectory.childDirectory('android'); - - processRunner.mockProcessesForExecutable['xcrun'] = [ - getMockXcodebuildListProcess( - ['RunnerTests', 'RunnerUITests']), // iOS list - MockProcess(), // iOS run - getMockXcodebuildListProcess( - ['RunnerTests', 'RunnerUITests']), // macOS list - MockProcess(), // macOS run - ]; - - final List output = await runCapturingPrint(runner, [ - 'native-test', - '--android', - '--ios', - '--macos', - kDestination, - 'foo_destination', - ]); - - expect( - output, - containsAll([ - contains('Running Android tests for plugin/example'), - contains('Successfully ran iOS xctest for plugin/example'), - contains('Successfully ran macOS xctest for plugin/example'), - ])); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall(androidFolder.childFile('gradlew').path, - const ['testDebugUnitTest'], androidFolder.path), - getTargetCheckCall(pluginExampleDirectory, 'ios'), - getRunTestCall(pluginExampleDirectory, 'ios', - destination: 'foo_destination'), - getTargetCheckCall(pluginExampleDirectory, 'macos'), - getRunTestCall(pluginExampleDirectory, 'macos'), - ])); - }); - - test('runs only macOS for a macOS plugin', () async { - final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir, - platformSupport: { - platformMacOS: const PlatformDetails(PlatformSupport.inline), - }); - - final Directory pluginExampleDirectory = getExampleDir(plugin); - - processRunner.mockProcessesForExecutable['xcrun'] = [ - getMockXcodebuildListProcess( - ['RunnerTests', 'RunnerUITests']), - ]; - - final List output = await runCapturingPrint(runner, [ - 'native-test', - '--ios', - '--macos', - kDestination, - 'foo_destination', - ]); - - expect( - output, - containsAllInOrder([ - contains('No implementation for iOS.'), - contains('Successfully ran macOS xctest for plugin/example'), - ])); - - expect( - processRunner.recordedCalls, - orderedEquals([ - getTargetCheckCall(pluginExampleDirectory, 'macos'), - getRunTestCall(pluginExampleDirectory, 'macos'), - ])); - }); - - test('runs only iOS for a iOS plugin', () async { - final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir, - platformSupport: { - platformIOS: const PlatformDetails(PlatformSupport.inline) - }); - - final Directory pluginExampleDirectory = getExampleDir(plugin); - - processRunner.mockProcessesForExecutable['xcrun'] = [ - getMockXcodebuildListProcess( - ['RunnerTests', 'RunnerUITests']), - ]; - - final List output = await runCapturingPrint(runner, [ - 'native-test', - '--ios', - '--macos', - kDestination, - 'foo_destination', - ]); - - expect( - output, - containsAllInOrder([ - contains('No implementation for macOS.'), - contains('Successfully ran iOS xctest for plugin/example') - ])); - - expect( - processRunner.recordedCalls, - orderedEquals([ - getTargetCheckCall(pluginExampleDirectory, 'ios'), - getRunTestCall(pluginExampleDirectory, 'ios', - destination: 'foo_destination'), - ])); - }); - - test('skips when nothing is supported', () async { - createFakePlugin('plugin', packagesDir); - - final List output = await runCapturingPrint(runner, [ - 'native-test', - '--android', - '--ios', - '--macos', - '--windows', - kDestination, - 'foo_destination', - ]); - - expect( - output, - containsAllInOrder([ - contains('No implementation for Android.'), - contains('No implementation for iOS.'), - contains('No implementation for macOS.'), - contains('SKIPPING: Nothing to test for target platform(s).'), - ])); - - expect(processRunner.recordedCalls, orderedEquals([])); - }); - - test('skips Dart-only plugins', () async { - createFakePlugin( - 'plugin', - packagesDir, - platformSupport: { - platformMacOS: const PlatformDetails(PlatformSupport.inline, - hasDartCode: true, hasNativeCode: false), - platformWindows: const PlatformDetails(PlatformSupport.inline, - hasDartCode: true, hasNativeCode: false), - }, - ); - - final List output = await runCapturingPrint(runner, [ - 'native-test', - '--macos', - '--windows', - kDestination, - 'foo_destination', - ]); - - expect( - output, - containsAllInOrder([ - contains('No native code for macOS.'), - contains('No native code for Windows.'), - contains('SKIPPING: Nothing to test for target platform(s).'), - ])); - - expect(processRunner.recordedCalls, orderedEquals([])); - }); - - test('failing one platform does not stop the tests', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', - packagesDir, - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline), - platformIOS: const PlatformDetails(PlatformSupport.inline), - }, - extraFiles: [ - 'example/android/gradlew', - 'example/android/app/src/test/example_test.java', - ], - ); - - processRunner.mockProcessesForExecutable['xcrun'] = [ - getMockXcodebuildListProcess( - ['RunnerTests', 'RunnerUITests']), - ]; - - // Simulate failing Android, but not iOS. - final String gradlewPath = plugin - .getExamples() - .first - .platformDirectory(FlutterPlatform.android) - .childFile('gradlew') - .path; - processRunner.mockProcessesForExecutable[gradlewPath] = [ - MockProcess(exitCode: 1) - ]; - - Error? commandError; - final List output = await runCapturingPrint(runner, [ - 'native-test', - '--android', - '--ios', - '--ios-destination', - 'foo_destination', - ], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - - expect( - output, - containsAllInOrder([ - contains('Running tests for Android...'), - contains('plugin/example unit tests failed.'), - contains('Running tests for iOS...'), - contains('Successfully ran iOS xctest for plugin/example'), - contains('The following packages had errors:'), - contains('plugin:\n' - ' Android') - ]), - ); - }); - - test('failing multiple platforms reports multiple failures', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', - packagesDir, - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline), - platformIOS: const PlatformDetails(PlatformSupport.inline), - }, - extraFiles: [ - 'example/android/gradlew', - 'example/android/app/src/test/example_test.java', - ], - ); - - // Simulate failing Android. - final String gradlewPath = plugin - .getExamples() - .first - .platformDirectory(FlutterPlatform.android) - .childFile('gradlew') - .path; - processRunner.mockProcessesForExecutable[gradlewPath] = [ - MockProcess(exitCode: 1) - ]; - // Simulate failing Android. - processRunner.mockProcessesForExecutable['xcrun'] = [ - MockProcess(exitCode: 1) - ]; - - Error? commandError; - final List output = await runCapturingPrint(runner, [ - 'native-test', - '--android', - '--ios', - '--ios-destination', - 'foo_destination', - ], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - - expect( - output, - containsAllInOrder([ - contains('Running tests for Android...'), - contains('Running tests for iOS...'), - contains('The following packages had errors:'), - contains('plugin:\n' - ' Android\n' - ' iOS') - ]), - ); - }); - }); - }); - - group('test native_test_command on Windows', () { - late FileSystem fileSystem; - late MockPlatform mockPlatform; - late Directory packagesDir; - late CommandRunner runner; - late RecordingProcessRunner processRunner; - - setUp(() { - fileSystem = MemoryFileSystem(style: FileSystemStyle.windows); - mockPlatform = MockPlatform(isWindows: true); - packagesDir = createPackagesDirectory(fileSystem: fileSystem); - processRunner = RecordingProcessRunner(); - final NativeTestCommand command = NativeTestCommand(packagesDir, - processRunner: processRunner, platform: mockPlatform); - - runner = CommandRunner( - 'native_test_command', 'Test for native_test_command'); - runner.addCommand(command); - }); - - // Returns the ProcessCall to expect for build the Windows unit tests for - // the given plugin. - ProcessCall getWindowsBuildCall(RepositoryPackage plugin) { - return ProcessCall( - _fakeCmakeCommand, - [ - '--build', - getExampleDir(plugin) - .childDirectory('build') - .childDirectory('windows') - .path, - '--target', - 'unit_tests', - '--config', - 'Debug' - ], - null); - } - - group('Windows', () { - test('runs unit tests', () async { - const String testBinaryRelativePath = - 'build/windows/Debug/bar/plugin_test.exe'; - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, extraFiles: [ - 'example/$testBinaryRelativePath' - ], platformSupport: { - platformWindows: const PlatformDetails(PlatformSupport.inline), - }); - _createFakeCMakeCache(plugin, mockPlatform); - - final File testBinary = childFileWithSubcomponents(plugin.directory, - ['example', ...testBinaryRelativePath.split('/')]); - - final List output = await runCapturingPrint(runner, [ - 'native-test', - '--windows', - '--no-integration', - ]); - - expect( - output, - containsAllInOrder([ - contains('Running plugin_test.exe...'), - contains('No issues found!'), - ]), - ); - - expect( - processRunner.recordedCalls, - orderedEquals([ - getWindowsBuildCall(plugin), - ProcessCall(testBinary.path, const [], null), - ])); - }); - - test('only runs debug unit tests', () async { - const String debugTestBinaryRelativePath = - 'build/windows/Debug/bar/plugin_test.exe'; - const String releaseTestBinaryRelativePath = - 'build/windows/Release/bar/plugin_test.exe'; - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, extraFiles: [ - 'example/$debugTestBinaryRelativePath', - 'example/$releaseTestBinaryRelativePath' - ], platformSupport: { - platformWindows: const PlatformDetails(PlatformSupport.inline), - }); - _createFakeCMakeCache(plugin, mockPlatform); - - final File debugTestBinary = childFileWithSubcomponents( - plugin.directory, - ['example', ...debugTestBinaryRelativePath.split('/')]); - - final List output = await runCapturingPrint(runner, [ - 'native-test', - '--windows', - '--no-integration', - ]); - - expect( - output, - containsAllInOrder([ - contains('Running plugin_test.exe...'), - contains('No issues found!'), - ]), - ); - - expect( - processRunner.recordedCalls, - orderedEquals([ - getWindowsBuildCall(plugin), - ProcessCall(debugTestBinary.path, const [], null), - ])); - }); - - test('fails if CMake has not been configured', () async { - createFakePlugin('plugin', packagesDir, - platformSupport: { - platformWindows: const PlatformDetails(PlatformSupport.inline), - }); - - Error? commandError; - final List output = await runCapturingPrint(runner, [ - 'native-test', - '--windows', - '--no-integration', - ], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('plugin:\n' - ' Examples must be built before testing.') - ]), - ); - - expect(processRunner.recordedCalls, orderedEquals([])); - }); - - test('fails if there are no unit tests', () async { - final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir, - platformSupport: { - platformWindows: const PlatformDetails(PlatformSupport.inline), - }); - _createFakeCMakeCache(plugin, mockPlatform); - - Error? commandError; - final List output = await runCapturingPrint(runner, [ - 'native-test', - '--windows', - '--no-integration', - ], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('No test binaries found.'), - ]), - ); - - expect( - processRunner.recordedCalls, - orderedEquals([ - getWindowsBuildCall(plugin), - ])); - }); - - test('fails if a unit test fails', () async { - const String testBinaryRelativePath = - 'build/windows/Debug/bar/plugin_test.exe'; - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, extraFiles: [ - 'example/$testBinaryRelativePath' - ], platformSupport: { - platformWindows: const PlatformDetails(PlatformSupport.inline), - }); - _createFakeCMakeCache(plugin, mockPlatform); - - final File testBinary = childFileWithSubcomponents(plugin.directory, - ['example', ...testBinaryRelativePath.split('/')]); - - processRunner.mockProcessesForExecutable[testBinary.path] = - [MockProcess(exitCode: 1)]; - - Error? commandError; - final List output = await runCapturingPrint(runner, [ - 'native-test', - '--windows', - '--no-integration', - ], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Running plugin_test.exe...'), - ]), - ); - - expect( - processRunner.recordedCalls, - orderedEquals([ - getWindowsBuildCall(plugin), - ProcessCall(testBinary.path, const [], null), - ])); - }); - }); - }); -} diff --git a/script/tool/test/podspec_check_command_test.dart b/script/tool/test/podspec_check_command_test.dart deleted file mode 100644 index c31ffd46a4b7..000000000000 --- a/script/tool/test/podspec_check_command_test.dart +++ /dev/null @@ -1,428 +0,0 @@ -// Copyright 2013 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. - -import 'dart:io' as io; - -import 'package:args/command_runner.dart'; -import 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:flutter_plugin_tools/src/common/core.dart'; -import 'package:flutter_plugin_tools/src/podspec_check_command.dart'; -import 'package:test/test.dart'; - -import 'mocks.dart'; -import 'util.dart'; - -/// Adds a fake podspec to [plugin]'s [platform] directory. -/// -/// If [includeSwiftWorkaround] is set, the xcconfig additions to make Swift -/// libraries work in apps that have no Swift will be included. If -/// [scopeSwiftWorkaround] is set, it will be specific to the iOS configuration. -void _writeFakePodspec(RepositoryPackage plugin, String platform, - {bool includeSwiftWorkaround = false, bool scopeSwiftWorkaround = false}) { - final String pluginName = plugin.directory.basename; - final File file = plugin.directory - .childDirectory(platform) - .childFile('$pluginName.podspec'); - final String swiftWorkaround = includeSwiftWorkaround - ? ''' - s.${scopeSwiftWorkaround ? 'ios.' : ''}xcconfig = { - 'LIBRARY_SEARCH_PATHS' => '\$(TOOLCHAIN_DIR)/usr/lib/swift/\$(PLATFORM_NAME)/ \$(SDKROOT)/usr/lib/swift', - 'LD_RUNPATH_SEARCH_PATHS' => '/usr/lib/swift', - } -''' - : ''; - file.createSync(recursive: true); - file.writeAsStringSync(''' -# -# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html -# -Pod::Spec.new do |s| - s.name = 'shared_preferences_foundation' - s.version = '0.0.1' - s.summary = 'iOS and macOS implementation of the shared_preferences plugin.' - s.description = <<-DESC -Wraps NSUserDefaults, providing a persistent store for simple key-value pairs. - DESC - s.homepage = 'https://github.com/flutter/plugins/tree/main/packages/shared_preferences/shared_preferences_foundation' - s.license = { :type => 'BSD', :file => '../LICENSE' } - s.author = { 'Flutter Team' => 'flutter-dev@googlegroups.com' } - s.source = { :http => 'https://github.com/flutter/plugins/tree/main/packages/shared_preferences/shared_preferences_foundation' } - s.source_files = 'Classes/**/*' - s.ios.dependency 'Flutter' - s.osx.dependency 'FlutterMacOS' - s.ios.deployment_target = '9.0' - s.osx.deployment_target = '10.11' - s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } - $swiftWorkaround - s.swift_version = '5.0' - -end -'''); -} - -void main() { - group('PodspecCheckCommand', () { - FileSystem fileSystem; - late Directory packagesDir; - late CommandRunner runner; - late MockPlatform mockPlatform; - late RecordingProcessRunner processRunner; - - setUp(() { - fileSystem = MemoryFileSystem(); - packagesDir = createPackagesDirectory(fileSystem: fileSystem); - - mockPlatform = MockPlatform(isMacOS: true); - processRunner = RecordingProcessRunner(); - final PodspecCheckCommand command = PodspecCheckCommand( - packagesDir, - processRunner: processRunner, - platform: mockPlatform, - ); - - runner = - CommandRunner('podspec_test', 'Test for $PodspecCheckCommand'); - runner.addCommand(command); - }); - - test('only runs on macOS', () async { - createFakePlugin('plugin1', packagesDir, - extraFiles: ['plugin1.podspec']); - mockPlatform.isMacOS = false; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['podspec-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - - expect( - processRunner.recordedCalls, - equals([]), - ); - - expect( - output, - containsAllInOrder( - [contains('only supported on macOS')], - )); - }); - - test('runs pod lib lint on a podspec', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin1', - packagesDir, - extraFiles: [ - 'bogus.dart', // Ignore non-podspecs. - ], - ); - _writeFakePodspec(plugin, 'ios'); - - processRunner.mockProcessesForExecutable['pod'] = [ - MockProcess(stdout: 'Foo', stderr: 'Bar'), - MockProcess(), - ]; - - final List output = - await runCapturingPrint(runner, ['podspec-check']); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall('which', const ['pod'], packagesDir.path), - ProcessCall( - 'pod', - [ - 'lib', - 'lint', - plugin - .platformDirectory(FlutterPlatform.ios) - .childFile('plugin1.podspec') - .path, - '--configuration=Debug', - '--skip-tests', - '--use-modular-headers', - '--use-libraries' - ], - packagesDir.path), - ProcessCall( - 'pod', - [ - 'lib', - 'lint', - plugin - .platformDirectory(FlutterPlatform.ios) - .childFile('plugin1.podspec') - .path, - '--configuration=Debug', - '--skip-tests', - '--use-modular-headers', - ], - packagesDir.path), - ]), - ); - - expect(output, contains('Linting plugin1.podspec')); - expect(output, contains('Foo')); - expect(output, contains('Bar')); - }); - - test('fails if pod is missing', () async { - final RepositoryPackage plugin = createFakePlugin('plugin1', packagesDir); - _writeFakePodspec(plugin, 'ios'); - - // Simulate failure from `which pod`. - processRunner.mockProcessesForExecutable['which'] = [ - MockProcess(exitCode: 1), - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['podspec-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - - expect( - output, - containsAllInOrder( - [ - contains('Unable to find "pod". Make sure it is in your path.'), - ], - )); - }); - - test('fails if linting as a framework fails', () async { - final RepositoryPackage plugin = createFakePlugin('plugin1', packagesDir); - _writeFakePodspec(plugin, 'ios'); - - // Simulate failure from `pod`. - processRunner.mockProcessesForExecutable['pod'] = [ - MockProcess(exitCode: 1), - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['podspec-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - - expect( - output, - containsAllInOrder( - [ - contains('The following packages had errors:'), - contains('plugin1:\n' - ' plugin1.podspec') - ], - )); - }); - - test('fails if linting as a static library fails', () async { - final RepositoryPackage plugin = createFakePlugin('plugin1', packagesDir); - _writeFakePodspec(plugin, 'ios'); - - // Simulate failure from the second call to `pod`. - processRunner.mockProcessesForExecutable['pod'] = [ - MockProcess(), - MockProcess(exitCode: 1), - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['podspec-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - - expect( - output, - containsAllInOrder( - [ - contains('The following packages had errors:'), - contains('plugin1:\n' - ' plugin1.podspec') - ], - )); - }); - - test('fails if an iOS Swift plugin is missing the search paths workaround', - () async { - final RepositoryPackage plugin = createFakePlugin('plugin1', packagesDir, - extraFiles: ['ios/Classes/SomeSwift.swift']); - _writeFakePodspec(plugin, 'ios'); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['podspec-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - - expect( - output, - containsAllInOrder( - [ - contains(r''' - s.xcconfig = { - 'LIBRARY_SEARCH_PATHS' => '$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)/ $(SDKROOT)/usr/lib/swift', - 'LD_RUNPATH_SEARCH_PATHS' => '/usr/lib/swift', - }'''), - contains('The following packages had errors:'), - contains('plugin1:\n' - ' plugin1.podspec') - ], - )); - }); - - test( - 'fails if a shared-source Swift plugin is missing the search paths workaround', - () async { - final RepositoryPackage plugin = createFakePlugin('plugin1', packagesDir, - extraFiles: ['darwin/Classes/SomeSwift.swift']); - _writeFakePodspec(plugin, 'darwin'); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['podspec-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - - expect( - output, - containsAllInOrder( - [ - contains(r''' - s.xcconfig = { - 'LIBRARY_SEARCH_PATHS' => '$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)/ $(SDKROOT)/usr/lib/swift', - 'LD_RUNPATH_SEARCH_PATHS' => '/usr/lib/swift', - }'''), - contains('The following packages had errors:'), - contains('plugin1:\n' - ' plugin1.podspec') - ], - )); - }); - - test('does not require the search paths workaround for macOS plugins', - () async { - final RepositoryPackage plugin = createFakePlugin('plugin1', packagesDir, - extraFiles: ['macos/Classes/SomeSwift.swift']); - _writeFakePodspec(plugin, 'macos'); - - final List output = - await runCapturingPrint(runner, ['podspec-check']); - - expect( - output, - containsAllInOrder( - [ - contains('Ran for 1 package(s)'), - ], - )); - }); - - test('does not require the search paths workaround for ObjC iOS plugins', - () async { - final RepositoryPackage plugin = createFakePlugin('plugin1', packagesDir, - extraFiles: [ - 'ios/Classes/SomeObjC.h', - 'ios/Classes/SomeObjC.m' - ]); - _writeFakePodspec(plugin, 'ios'); - - final List output = - await runCapturingPrint(runner, ['podspec-check']); - - expect( - output, - containsAllInOrder( - [ - contains('Ran for 1 package(s)'), - ], - )); - }); - - test('passes if the search paths workaround is present', () async { - final RepositoryPackage plugin = createFakePlugin('plugin1', packagesDir, - extraFiles: ['ios/Classes/SomeSwift.swift']); - _writeFakePodspec(plugin, 'ios', includeSwiftWorkaround: true); - - final List output = - await runCapturingPrint(runner, ['podspec-check']); - - expect( - output, - containsAllInOrder( - [ - contains('Ran for 1 package(s)'), - ], - )); - }); - - test('passes if the search paths workaround is present for iOS only', - () async { - final RepositoryPackage plugin = createFakePlugin('plugin1', packagesDir, - extraFiles: ['ios/Classes/SomeSwift.swift']); - _writeFakePodspec(plugin, 'ios', - includeSwiftWorkaround: true, scopeSwiftWorkaround: true); - - final List output = - await runCapturingPrint(runner, ['podspec-check']); - - expect( - output, - containsAllInOrder( - [ - contains('Ran for 1 package(s)'), - ], - )); - }); - - test('does not require the search paths workaround for Swift example code', - () async { - final RepositoryPackage plugin = - createFakePlugin('plugin1', packagesDir, extraFiles: [ - 'ios/Classes/SomeObjC.h', - 'ios/Classes/SomeObjC.m', - 'example/ios/Runner/AppDelegate.swift', - ]); - _writeFakePodspec(plugin, 'ios'); - - final List output = - await runCapturingPrint(runner, ['podspec-check']); - - expect( - output, - containsAllInOrder( - [ - contains('Ran for 1 package(s)'), - ], - )); - }); - - test('skips when there are no podspecs', () async { - createFakePlugin('plugin1', packagesDir); - - final List output = - await runCapturingPrint(runner, ['podspec-check']); - - expect( - output, - containsAllInOrder( - [contains('SKIPPING: No podspecs.')], - )); - }); - }); -} diff --git a/script/tool/test/publish_check_command_test.dart b/script/tool/test/publish_check_command_test.dart deleted file mode 100644 index 575f8509fd25..000000000000 --- a/script/tool/test/publish_check_command_test.dart +++ /dev/null @@ -1,464 +0,0 @@ -// Copyright 2013 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. - -import 'dart:convert'; -import 'dart:io' as io; - -import 'package:args/command_runner.dart'; -import 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:flutter_plugin_tools/src/common/core.dart'; -import 'package:flutter_plugin_tools/src/publish_check_command.dart'; -import 'package:http/http.dart' as http; -import 'package:http/testing.dart'; -import 'package:test/test.dart'; - -import 'mocks.dart'; -import 'util.dart'; - -void main() { - group('$PublishCheckCommand tests', () { - FileSystem fileSystem; - late MockPlatform mockPlatform; - late Directory packagesDir; - late RecordingProcessRunner processRunner; - late CommandRunner runner; - - setUp(() { - fileSystem = MemoryFileSystem(); - mockPlatform = MockPlatform(); - packagesDir = createPackagesDirectory(fileSystem: fileSystem); - processRunner = RecordingProcessRunner(); - final PublishCheckCommand publishCheckCommand = PublishCheckCommand( - packagesDir, - processRunner: processRunner, - platform: mockPlatform, - ); - - runner = CommandRunner( - 'publish_check_command', - 'Test for publish-check command.', - ); - runner.addCommand(publishCheckCommand); - }); - - test('publish check all packages', () async { - final RepositoryPackage plugin1 = createFakePlugin( - 'plugin_tools_test_package_a', - packagesDir, - examples: [], - ); - final RepositoryPackage plugin2 = createFakePlugin( - 'plugin_tools_test_package_b', - packagesDir, - examples: [], - ); - - await runCapturingPrint(runner, ['publish-check']); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - 'flutter', - const ['pub', 'publish', '--', '--dry-run'], - plugin1.path), - ProcessCall( - 'flutter', - const ['pub', 'publish', '--', '--dry-run'], - plugin2.path), - ])); - }); - - test('publish prepares dependencies of examples (when present)', () async { - final RepositoryPackage plugin1 = createFakePlugin( - 'plugin_tools_test_package_a', - packagesDir, - examples: ['example1', 'example2'], - ); - final RepositoryPackage plugin2 = createFakePlugin( - 'plugin_tools_test_package_b', - packagesDir, - examples: [], - ); - - await runCapturingPrint(runner, ['publish-check']); - - // For plugin1, these are the expected pub get calls that will happen - final Iterable pubGetCalls = - plugin1.getExamples().map((RepositoryPackage example) { - return ProcessCall( - 'dart', - const ['pub', 'get'], - example.path, - ); - }); - - expect(pubGetCalls, hasLength(2)); - expect( - processRunner.recordedCalls, - orderedEquals([ - // plugin1 has 2 examples, so there's some 'dart pub get' calls. - ...pubGetCalls, - ProcessCall( - 'flutter', - const ['pub', 'publish', '--', '--dry-run'], - plugin1.path), - // plugin2 has no examples, so there's no extra 'dart pub get' calls. - ProcessCall( - 'flutter', - const ['pub', 'publish', '--', '--dry-run'], - plugin2.path), - ]), - ); - }); - - test('fail on negative test', () async { - createFakePlugin('plugin_tools_test_package_a', packagesDir); - - processRunner.mockProcessesForExecutable['flutter'] = [ - MockProcess(exitCode: 1, stdout: 'Some error from pub') - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['publish-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Some error from pub'), - contains('Unable to publish plugin_tools_test_package_a'), - ]), - ); - }); - - test('fail on bad pubspec', () async { - final RepositoryPackage package = createFakePlugin('c', packagesDir); - await package.pubspecFile.writeAsString('bad-yaml'); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['publish-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('No valid pubspec found.'), - ]), - ); - }); - - test('fails if AUTHORS is missing', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir); - package.authorsFile.delete(); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['publish-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains( - 'No AUTHORS file found. Packages must include an AUTHORS file.'), - ]), - ); - }); - - test('does not require AUTHORS for third-party', () async { - final RepositoryPackage package = createFakePackage( - 'a_package', - packagesDir.parent - .childDirectory('third_party') - .childDirectory('packages')); - package.authorsFile.delete(); - - final List output = - await runCapturingPrint(runner, ['publish-check']); - - expect( - output, - containsAllInOrder([ - contains('Running for a_package'), - ]), - ); - }); - - test('pass on prerelease if --allow-pre-release flag is on', () async { - createFakePlugin('d', packagesDir); - - final MockProcess process = MockProcess( - exitCode: 1, - stdout: 'Package has 1 warning.\n' - 'Packages with an SDK constraint on a pre-release of the Dart ' - 'SDK should themselves be published as a pre-release version.'); - processRunner.mockProcessesForExecutable['flutter'] = [ - process, - ]; - - expect( - runCapturingPrint( - runner, ['publish-check', '--allow-pre-release']), - completes); - }); - - test('fail on prerelease if --allow-pre-release flag is off', () async { - createFakePlugin('d', packagesDir); - - final MockProcess process = MockProcess( - exitCode: 1, - stdout: 'Package has 1 warning.\n' - 'Packages with an SDK constraint on a pre-release of the Dart ' - 'SDK should themselves be published as a pre-release version.'); - processRunner.mockProcessesForExecutable['flutter'] = [ - process, - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['publish-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains( - 'Packages with an SDK constraint on a pre-release of the Dart SDK'), - contains('Unable to publish d'), - ]), - ); - }); - - test('Success message on stderr is not printed as an error', () async { - createFakePlugin('d', packagesDir); - - processRunner.mockProcessesForExecutable['flutter'] = [ - MockProcess(stdout: 'Package has 0 warnings.'), - ]; - - final List output = - await runCapturingPrint(runner, ['publish-check']); - - expect(output, isNot(contains(contains('ERROR:')))); - }); - - test( - '--machine: Log JSON with status:no-publish and correct human message, if there are no packages need to be published. ', - () async { - const Map httpResponseA = { - 'name': 'a', - 'versions': [ - '0.0.1', - '0.1.0', - ], - }; - - const Map httpResponseB = { - 'name': 'b', - 'versions': [ - '0.0.1', - '0.1.0', - '0.2.0', - ], - }; - - final MockClient mockClient = MockClient((http.Request request) async { - if (request.url.pathSegments.last == 'no_publish_a.json') { - return http.Response(json.encode(httpResponseA), 200); - } else if (request.url.pathSegments.last == 'no_publish_b.json') { - return http.Response(json.encode(httpResponseB), 200); - } - return http.Response('', 500); - }); - final PublishCheckCommand command = PublishCheckCommand(packagesDir, - processRunner: processRunner, httpClient: mockClient); - - runner = CommandRunner( - 'publish_check_command', - 'Test for publish-check command.', - ); - runner.addCommand(command); - - createFakePlugin('no_publish_a', packagesDir, version: '0.1.0'); - createFakePlugin('no_publish_b', packagesDir, version: '0.2.0'); - - final List output = await runCapturingPrint( - runner, ['publish-check', '--machine']); - - expect(output.first, r''' -{ - "status": "no-publish", - "humanMessage": [ - "\n============================================================\n|| Running for no_publish_a\n============================================================\n", - "Package no_publish_a version: 0.1.0 has already be published on pub.", - "\n============================================================\n|| Running for no_publish_b\n============================================================\n", - "Package no_publish_b version: 0.2.0 has already be published on pub.", - "\n", - "------------------------------------------------------------", - "Run overview:", - " no_publish_a - ran", - " no_publish_b - ran", - "", - "Ran for 2 package(s)", - "\n", - "No issues found!" - ] -}'''); - }); - - test( - '--machine: Log JSON with status:needs-publish and correct human message, if there is at least 1 plugin needs to be published.', - () async { - const Map httpResponseA = { - 'name': 'a', - 'versions': [ - '0.0.1', - '0.1.0', - ], - }; - - const Map httpResponseB = { - 'name': 'b', - 'versions': [ - '0.0.1', - '0.1.0', - ], - }; - - final MockClient mockClient = MockClient((http.Request request) async { - if (request.url.pathSegments.last == 'no_publish_a.json') { - return http.Response(json.encode(httpResponseA), 200); - } else if (request.url.pathSegments.last == 'no_publish_b.json') { - return http.Response(json.encode(httpResponseB), 200); - } - return http.Response('', 500); - }); - final PublishCheckCommand command = PublishCheckCommand(packagesDir, - processRunner: processRunner, httpClient: mockClient); - - runner = CommandRunner( - 'publish_check_command', - 'Test for publish-check command.', - ); - runner.addCommand(command); - - createFakePlugin('no_publish_a', packagesDir, version: '0.1.0'); - createFakePlugin('no_publish_b', packagesDir, version: '0.2.0'); - - final List output = await runCapturingPrint( - runner, ['publish-check', '--machine']); - - expect(output.first, r''' -{ - "status": "needs-publish", - "humanMessage": [ - "\n============================================================\n|| Running for no_publish_a\n============================================================\n", - "Package no_publish_a version: 0.1.0 has already be published on pub.", - "\n============================================================\n|| Running for no_publish_b\n============================================================\n", - "Running pub publish --dry-run:", - "Package no_publish_b is able to be published.", - "\n", - "------------------------------------------------------------", - "Run overview:", - " no_publish_a - ran", - " no_publish_b - ran", - "", - "Ran for 2 package(s)", - "\n", - "No issues found!" - ] -}'''); - }); - - test( - '--machine: Log correct JSON, if there is at least 1 plugin contains error.', - () async { - const Map httpResponseA = { - 'name': 'a', - 'versions': [ - '0.0.1', - '0.1.0', - ], - }; - - const Map httpResponseB = { - 'name': 'b', - 'versions': [ - '0.0.1', - '0.1.0', - ], - }; - - final MockClient mockClient = MockClient((http.Request request) async { - print('url ${request.url}'); - print(request.url.pathSegments.last); - if (request.url.pathSegments.last == 'no_publish_a.json') { - return http.Response(json.encode(httpResponseA), 200); - } else if (request.url.pathSegments.last == 'no_publish_b.json') { - return http.Response(json.encode(httpResponseB), 200); - } - return http.Response('', 500); - }); - final PublishCheckCommand command = PublishCheckCommand(packagesDir, - processRunner: processRunner, httpClient: mockClient); - - runner = CommandRunner( - 'publish_check_command', - 'Test for publish-check command.', - ); - runner.addCommand(command); - - final RepositoryPackage plugin = - createFakePlugin('no_publish_a', packagesDir, version: '0.1.0'); - createFakePlugin('no_publish_b', packagesDir, version: '0.2.0'); - - await plugin.pubspecFile.writeAsString('bad-yaml'); - - bool hasError = false; - final List output = await runCapturingPrint( - runner, ['publish-check', '--machine'], - errorHandler: (Error error) { - expect(error, isA()); - hasError = true; - }); - expect(hasError, isTrue); - - expect(output.first, contains(r''' -{ - "status": "error", - "humanMessage": [ - "\n============================================================\n|| Running for no_publish_a\n============================================================\n", - "Failed to parse `pubspec.yaml` at /packages/no_publish_a/pubspec.yaml: ParsedYamlException:''')); - // This is split into two checks since the details of the YamlException - // aren't controlled by this package, so asserting its exact format would - // make the test fragile to irrelevant changes in those details. - expect(output.first, contains(r''' - "No valid pubspec found.", - "\n============================================================\n|| Running for no_publish_b\n============================================================\n", - "url https://pub.dev/packages/no_publish_b.json", - "no_publish_b.json", - "Running pub publish --dry-run:", - "Package no_publish_b is able to be published.", - "\n", - "The following packages had errors:", - " no_publish_a", - "See above for full details." - ] -}''')); - }); - }); -} diff --git a/script/tool/test/publish_command_test.dart b/script/tool/test/publish_command_test.dart deleted file mode 100644 index da5f9c871f05..000000000000 --- a/script/tool/test/publish_command_test.dart +++ /dev/null @@ -1,922 +0,0 @@ -// Copyright 2013 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. - -import 'dart:async'; -import 'dart:convert'; -import 'dart:io' as io; - -import 'package:args/command_runner.dart'; -import 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:flutter_plugin_tools/src/common/core.dart'; -import 'package:flutter_plugin_tools/src/publish_command.dart'; -import 'package:http/http.dart' as http; -import 'package:http/testing.dart'; -import 'package:mockito/mockito.dart'; -import 'package:platform/platform.dart'; -import 'package:test/test.dart'; - -import 'common/package_command_test.mocks.dart'; -import 'mocks.dart'; -import 'util.dart'; - -void main() { - final String flutterCommand = getFlutterCommand(const LocalPlatform()); - - late Directory packagesDir; - late MockGitDir gitDir; - late TestProcessRunner processRunner; - late CommandRunner commandRunner; - late MockStdin mockStdin; - late FileSystem fileSystem; - // Map of package name to mock response. - late Map> mockHttpResponses; - - void createMockCredentialFile() { - final String credentialPath = PublishCommand.getCredentialPath(); - fileSystem.file(credentialPath) - ..createSync(recursive: true) - ..writeAsStringSync('some credential'); - } - - setUp(() async { - fileSystem = MemoryFileSystem(); - packagesDir = createPackagesDirectory(fileSystem: fileSystem); - processRunner = TestProcessRunner(); - - mockHttpResponses = >{}; - final MockClient mockClient = MockClient((http.Request request) async { - final String packageName = - request.url.pathSegments.last.replaceAll('.json', ''); - final Map? response = mockHttpResponses[packageName]; - if (response != null) { - return http.Response(json.encode(response), 200); - } - // Default to simulating the plugin never having been published. - return http.Response('', 404); - }); - - gitDir = MockGitDir(); - when(gitDir.path).thenReturn(packagesDir.parent.path); - when(gitDir.runCommand(any, throwOnError: anyNamed('throwOnError'))) - .thenAnswer((Invocation invocation) { - final List arguments = - invocation.positionalArguments[0]! as List; - // Route git calls through the process runner, to make mock output - // consistent with outer processes. Attach the first argument to the - // command to make targeting the mock results easier. - final String gitCommand = arguments.removeAt(0); - return processRunner.run('git-$gitCommand', arguments); - }); - - mockStdin = MockStdin(); - commandRunner = CommandRunner('tester', '') - ..addCommand(PublishCommand( - packagesDir, - processRunner: processRunner, - stdinput: mockStdin, - gitDir: gitDir, - httpClient: mockClient, - )); - }); - - group('Initial validation', () { - test('refuses to proceed with dirty files', () async { - final RepositoryPackage plugin = - createFakePlugin('foo', packagesDir, examples: []); - - processRunner.mockProcessesForExecutable['git-status'] = [ - MockProcess(stdout: '?? ${plugin.directory.childFile('tmp').path}\n') - ]; - - Error? commandError; - final List output = - await runCapturingPrint(commandRunner, [ - 'publish', - '--packages=foo', - ], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains("There are files in the package directory that haven't " - 'been saved in git. Refusing to publish these files:\n\n' - '?? /packages/foo/tmp\n\n' - 'If the directory should be clean, you can run `git clean -xdf && ' - 'git reset --hard HEAD` to wipe all local changes.'), - contains('foo:\n' - ' uncommitted changes'), - ])); - }); - - test("fails immediately if the remote doesn't exist", () async { - createFakePlugin('foo', packagesDir, examples: []); - - processRunner.mockProcessesForExecutable['git-remote'] = [ - MockProcess(exitCode: 1), - ]; - - Error? commandError; - final List output = await runCapturingPrint( - commandRunner, ['publish', '--packages=foo'], - errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains( - 'Unable to find URL for remote upstream; cannot push tags'), - ])); - }); - }); - - group('Publishes package', () { - test('while showing all output from pub publish to the user', () async { - createFakePlugin('plugin1', packagesDir, examples: []); - createFakePlugin('plugin2', packagesDir, examples: []); - - processRunner.mockProcessesForExecutable[flutterCommand] = [ - MockProcess( - stdout: 'Foo', - stderr: 'Bar', - stdoutEncoding: utf8, - stderrEncoding: utf8), // pub publish for plugin1 - MockProcess( - stdout: 'Baz', - stdoutEncoding: utf8, - stderrEncoding: utf8), // pub publish for plugin1 - ]; - - final List output = await runCapturingPrint( - commandRunner, ['publish', '--packages=plugin1,plugin2']); - - expect( - output, - containsAllInOrder([ - contains('Running `pub publish ` in /packages/plugin1...'), - contains('Foo'), - contains('Bar'), - contains('Package published!'), - contains('Running `pub publish ` in /packages/plugin2...'), - contains('Baz'), - contains('Package published!'), - ])); - }); - - test('forwards input from the user to `pub publish`', () async { - createFakePlugin('foo', packagesDir, examples: []); - - mockStdin.mockUserInputs.add(utf8.encode('user input')); - - await runCapturingPrint( - commandRunner, ['publish', '--packages=foo']); - - expect(processRunner.mockPublishProcess.stdinMock.lines, - contains('user input')); - }); - - test('forwards --pub-publish-flags to pub publish', () async { - final RepositoryPackage plugin = - createFakePlugin('foo', packagesDir, examples: []); - - await runCapturingPrint(commandRunner, [ - 'publish', - '--packages=foo', - '--pub-publish-flags', - '--dry-run,--server=bar' - ]); - - expect( - processRunner.recordedCalls, - contains(ProcessCall( - flutterCommand, - const ['pub', 'publish', '--dry-run', '--server=bar'], - plugin.path))); - }); - - test( - '--skip-confirmation flag automatically adds --force to --pub-publish-flags', - () async { - createMockCredentialFile(); - final RepositoryPackage plugin = - createFakePlugin('foo', packagesDir, examples: []); - - await runCapturingPrint(commandRunner, [ - 'publish', - '--packages=foo', - '--skip-confirmation', - '--pub-publish-flags', - '--server=bar' - ]); - - expect( - processRunner.recordedCalls, - contains(ProcessCall( - flutterCommand, - const ['pub', 'publish', '--server=bar', '--force'], - plugin.path))); - }); - - test('--force is only added once, regardless of plugin count', () async { - createMockCredentialFile(); - final RepositoryPackage plugin1 = - createFakePlugin('plugin_a', packagesDir, examples: []); - final RepositoryPackage plugin2 = - createFakePlugin('plugin_b', packagesDir, examples: []); - - await runCapturingPrint(commandRunner, [ - 'publish', - '--packages=plugin_a,plugin_b', - '--skip-confirmation', - '--pub-publish-flags', - '--server=bar' - ]); - - expect( - processRunner.recordedCalls, - containsAllInOrder([ - ProcessCall( - flutterCommand, - const ['pub', 'publish', '--server=bar', '--force'], - plugin1.path), - ProcessCall( - flutterCommand, - const ['pub', 'publish', '--server=bar', '--force'], - plugin2.path), - ])); - }); - - test('throws if pub publish fails', () async { - createFakePlugin('foo', packagesDir, examples: []); - - processRunner.mockProcessesForExecutable[flutterCommand] = [ - MockProcess(exitCode: 128) // pub publish - ]; - - Error? commandError; - final List output = - await runCapturingPrint(commandRunner, [ - 'publish', - '--packages=foo', - ], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Publishing foo failed.'), - ])); - }); - - test('publish, dry run', () async { - final RepositoryPackage plugin = - createFakePlugin('foo', packagesDir, examples: []); - - final List output = - await runCapturingPrint(commandRunner, [ - 'publish', - '--packages=foo', - '--dry-run', - ]); - - expect( - processRunner.recordedCalls - .map((ProcessCall call) => call.executable), - isNot(contains('git-push'))); - expect( - output, - containsAllInOrder([ - contains('=============== DRY RUN ==============='), - contains('Running for foo'), - contains('Running `pub publish ` in ${plugin.path}...'), - contains('Tagging release foo-v0.0.1...'), - contains('Pushing tag to upstream...'), - contains('Published foo successfully!'), - ])); - }); - - test('can publish non-flutter package', () async { - const String packageName = 'a_package'; - createFakePackage(packageName, packagesDir); - - final List output = - await runCapturingPrint(commandRunner, [ - 'publish', - '--packages=$packageName', - ]); - - expect( - output, - containsAllInOrder( - [ - contains('Running `pub publish ` in /packages/a_package...'), - contains('Package published!'), - ], - ), - ); - }); - }); - - group('Tags release', () { - test('with the version and name from the pubspec.yaml', () async { - createFakePlugin('foo', packagesDir, examples: []); - await runCapturingPrint(commandRunner, [ - 'publish', - '--packages=foo', - ]); - - expect(processRunner.recordedCalls, - contains(const ProcessCall('git-tag', ['foo-v0.0.1'], null))); - }); - - test('only if publishing succeeded', () async { - createFakePlugin('foo', packagesDir, examples: []); - - processRunner.mockProcessesForExecutable[flutterCommand] = [ - MockProcess(exitCode: 128) // pub publish - ]; - - Error? commandError; - final List output = - await runCapturingPrint(commandRunner, [ - 'publish', - '--packages=foo', - ], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Publishing foo failed.'), - ])); - expect( - processRunner.recordedCalls, - isNot(contains( - const ProcessCall('git-tag', ['foo-v0.0.1'], null)))); - }); - }); - - group('Pushes tags', () { - test('to upstream by default', () async { - createFakePlugin('foo', packagesDir, examples: []); - - mockStdin.readLineOutput = 'y'; - - final List output = - await runCapturingPrint(commandRunner, [ - 'publish', - '--packages=foo', - ]); - - expect( - processRunner.recordedCalls, - contains(const ProcessCall( - 'git-push', ['upstream', 'foo-v0.0.1'], null))); - expect( - output, - containsAllInOrder([ - contains('Pushing tag to upstream...'), - contains('Published foo successfully!'), - ])); - }); - - test('does not ask for user input if the --skip-confirmation flag is on', - () async { - createMockCredentialFile(); - createFakePlugin('foo', packagesDir, examples: []); - - final List output = - await runCapturingPrint(commandRunner, [ - 'publish', - '--skip-confirmation', - '--packages=foo', - ]); - - expect( - processRunner.recordedCalls, - contains(const ProcessCall( - 'git-push', ['upstream', 'foo-v0.0.1'], null))); - expect( - output, - containsAllInOrder([ - contains('Published foo successfully!'), - ])); - }); - - test('to upstream by default, dry run', () async { - final RepositoryPackage plugin = - createFakePlugin('foo', packagesDir, examples: []); - - mockStdin.readLineOutput = 'y'; - - final List output = await runCapturingPrint( - commandRunner, ['publish', '--packages=foo', '--dry-run']); - - expect( - processRunner.recordedCalls - .map((ProcessCall call) => call.executable), - isNot(contains('git-push'))); - expect( - output, - containsAllInOrder([ - contains('=============== DRY RUN ==============='), - contains('Running `pub publish ` in ${plugin.path}...'), - contains('Tagging release foo-v0.0.1...'), - contains('Pushing tag to upstream...'), - contains('Published foo successfully!'), - ])); - }); - - test('to different remotes based on a flag', () async { - createFakePlugin('foo', packagesDir, examples: []); - - mockStdin.readLineOutput = 'y'; - - final List output = - await runCapturingPrint(commandRunner, [ - 'publish', - '--packages=foo', - '--remote', - 'origin', - ]); - - expect( - processRunner.recordedCalls, - contains(const ProcessCall( - 'git-push', ['origin', 'foo-v0.0.1'], null))); - expect( - output, - containsAllInOrder([ - contains('Published foo successfully!'), - ])); - }); - }); - - group('Auto release (all-changed flag)', () { - test('can release newly created plugins', () async { - mockHttpResponses['plugin1'] = { - 'name': 'plugin1', - 'versions': [], - }; - - mockHttpResponses['plugin2'] = { - 'name': 'plugin2', - 'versions': [], - }; - - // Non-federated - final RepositoryPackage plugin1 = - createFakePlugin('plugin1', packagesDir); - // federated - final RepositoryPackage plugin2 = createFakePlugin( - 'plugin2', - packagesDir.childDirectory('plugin2'), - ); - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess( - stdout: '${plugin1.pubspecFile.path}\n' - '${plugin2.pubspecFile.path}\n') - ]; - mockStdin.readLineOutput = 'y'; - - final List output = await runCapturingPrint(commandRunner, - ['publish', '--all-changed', '--base-sha=HEAD~']); - - expect( - output, - containsAllInOrder([ - contains( - 'Publishing all packages that have changed relative to "HEAD~"'), - contains('Running `pub publish ` in ${plugin1.path}...'), - contains('Running `pub publish ` in ${plugin2.path}...'), - contains('plugin1 - \x1B[32mpublished\x1B[0m'), - contains('plugin2/plugin2 - \x1B[32mpublished\x1B[0m'), - ])); - expect( - processRunner.recordedCalls, - contains(const ProcessCall( - 'git-push', ['upstream', 'plugin1-v0.0.1'], null))); - expect( - processRunner.recordedCalls, - contains(const ProcessCall( - 'git-push', ['upstream', 'plugin2-v0.0.1'], null))); - }); - - test('can release newly created plugins, while there are existing plugins', - () async { - mockHttpResponses['plugin0'] = { - 'name': 'plugin0', - 'versions': ['0.0.1'], - }; - - mockHttpResponses['plugin1'] = { - 'name': 'plugin1', - 'versions': [], - }; - - mockHttpResponses['plugin2'] = { - 'name': 'plugin2', - 'versions': [], - }; - - // The existing plugin. - createFakePlugin('plugin0', packagesDir); - // Non-federated - final RepositoryPackage plugin1 = - createFakePlugin('plugin1', packagesDir); - // federated - final RepositoryPackage plugin2 = - createFakePlugin('plugin2', packagesDir.childDirectory('plugin2')); - - // Git results for plugin0 having been released already, and plugin1 and - // plugin2 being new. - processRunner.mockProcessesForExecutable['git-tag'] = [ - MockProcess(stdout: 'plugin0-v0.0.1\n') - ]; - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess( - stdout: '${plugin1.pubspecFile.path}\n' - '${plugin2.pubspecFile.path}\n') - ]; - - mockStdin.readLineOutput = 'y'; - - final List output = await runCapturingPrint(commandRunner, - ['publish', '--all-changed', '--base-sha=HEAD~']); - - expect( - output, - containsAllInOrder([ - 'Running `pub publish ` in ${plugin1.path}...\n', - 'Running `pub publish ` in ${plugin2.path}...\n', - ])); - expect( - processRunner.recordedCalls, - contains(const ProcessCall( - 'git-push', ['upstream', 'plugin1-v0.0.1'], null))); - expect( - processRunner.recordedCalls, - contains(const ProcessCall( - 'git-push', ['upstream', 'plugin2-v0.0.1'], null))); - }); - - test('can release newly created plugins, dry run', () async { - mockHttpResponses['plugin1'] = { - 'name': 'plugin1', - 'versions': [], - }; - - mockHttpResponses['plugin2'] = { - 'name': 'plugin2', - 'versions': [], - }; - - // Non-federated - final RepositoryPackage plugin1 = - createFakePlugin('plugin1', packagesDir); - // federated - final RepositoryPackage plugin2 = - createFakePlugin('plugin2', packagesDir.childDirectory('plugin2')); - - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess( - stdout: '${plugin1.pubspecFile.path}\n' - '${plugin2.pubspecFile.path}\n') - ]; - mockStdin.readLineOutput = 'y'; - - final List output = await runCapturingPrint( - commandRunner, [ - 'publish', - '--all-changed', - '--base-sha=HEAD~', - '--dry-run' - ]); - - expect( - output, - containsAllInOrder([ - contains('=============== DRY RUN ==============='), - contains('Running `pub publish ` in ${plugin1.path}...'), - contains('Tagging release plugin1-v0.0.1...'), - contains('Pushing tag to upstream...'), - contains('Published plugin1 successfully!'), - contains('Running `pub publish ` in ${plugin2.path}...'), - contains('Tagging release plugin2-v0.0.1...'), - contains('Pushing tag to upstream...'), - contains('Published plugin2 successfully!'), - ])); - expect( - processRunner.recordedCalls - .map((ProcessCall call) => call.executable), - isNot(contains('git-push'))); - }); - - test('version change triggers releases.', () async { - mockHttpResponses['plugin1'] = { - 'name': 'plugin1', - 'versions': ['0.0.1'], - }; - - mockHttpResponses['plugin2'] = { - 'name': 'plugin2', - 'versions': ['0.0.1'], - }; - - // Non-federated - final RepositoryPackage plugin1 = - createFakePlugin('plugin1', packagesDir, version: '0.0.2'); - // federated - final RepositoryPackage plugin2 = createFakePlugin( - 'plugin2', packagesDir.childDirectory('plugin2'), - version: '0.0.2'); - - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess( - stdout: '${plugin1.pubspecFile.path}\n' - '${plugin2.pubspecFile.path}\n') - ]; - - mockStdin.readLineOutput = 'y'; - - final List output2 = await runCapturingPrint(commandRunner, - ['publish', '--all-changed', '--base-sha=HEAD~']); - expect( - output2, - containsAllInOrder([ - contains('Running `pub publish ` in ${plugin1.path}...'), - contains('Published plugin1 successfully!'), - contains('Running `pub publish ` in ${plugin2.path}...'), - contains('Published plugin2 successfully!'), - ])); - expect( - processRunner.recordedCalls, - contains(const ProcessCall( - 'git-push', ['upstream', 'plugin1-v0.0.2'], null))); - expect( - processRunner.recordedCalls, - contains(const ProcessCall( - 'git-push', ['upstream', 'plugin2-v0.0.2'], null))); - }); - - test( - 'delete package will not trigger publish but exit the command successfully!', - () async { - mockHttpResponses['plugin1'] = { - 'name': 'plugin1', - 'versions': ['0.0.1'], - }; - - mockHttpResponses['plugin2'] = { - 'name': 'plugin2', - 'versions': ['0.0.1'], - }; - - // Non-federated - final RepositoryPackage plugin1 = - createFakePlugin('plugin1', packagesDir, version: '0.0.2'); - // federated - final RepositoryPackage plugin2 = - createFakePlugin('plugin2', packagesDir.childDirectory('plugin2')); - plugin2.directory.deleteSync(recursive: true); - - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess( - stdout: '${plugin1.pubspecFile.path}\n' - '${plugin2.pubspecFile.path}\n') - ]; - - mockStdin.readLineOutput = 'y'; - - final List output2 = await runCapturingPrint(commandRunner, - ['publish', '--all-changed', '--base-sha=HEAD~']); - expect( - output2, - containsAllInOrder([ - contains('Running `pub publish ` in ${plugin1.path}...'), - contains('Published plugin1 successfully!'), - contains( - 'The pubspec file for plugin2/plugin2 does not exist, so no publishing will happen.\nSafe to ignore if the package is deleted in this commit.\n'), - contains('SKIPPING: package deleted'), - contains('skipped (with warning)'), - ])); - expect( - processRunner.recordedCalls, - contains(const ProcessCall( - 'git-push', ['upstream', 'plugin1-v0.0.2'], null))); - }); - - test('Existing versions do not trigger release, also prints out message.', - () async { - mockHttpResponses['plugin1'] = { - 'name': 'plugin1', - 'versions': ['0.0.2'], - }; - - mockHttpResponses['plugin2'] = { - 'name': 'plugin2', - 'versions': ['0.0.2'], - }; - - // Non-federated - final RepositoryPackage plugin1 = - createFakePlugin('plugin1', packagesDir, version: '0.0.2'); - // federated - final RepositoryPackage plugin2 = createFakePlugin( - 'plugin2', packagesDir.childDirectory('plugin2'), - version: '0.0.2'); - - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess( - stdout: '${plugin1.pubspecFile.path}\n' - '${plugin2.pubspecFile.path}\n') - ]; - processRunner.mockProcessesForExecutable['git-tag'] = [ - MockProcess( - stdout: 'plugin1-v0.0.2\n' - 'plugin2-v0.0.2\n') - ]; - - final List output = await runCapturingPrint(commandRunner, - ['publish', '--all-changed', '--base-sha=HEAD~']); - - expect( - output, - containsAllInOrder([ - contains('plugin1 0.0.2 has already been published'), - contains('SKIPPING: already published'), - contains('plugin2 0.0.2 has already been published'), - contains('SKIPPING: already published'), - ])); - - expect( - processRunner.recordedCalls - .map((ProcessCall call) => call.executable), - isNot(contains('git-push'))); - }); - - test( - 'Existing versions do not trigger release, but fail if the tags do not exist.', - () async { - mockHttpResponses['plugin1'] = { - 'name': 'plugin1', - 'versions': ['0.0.2'], - }; - - mockHttpResponses['plugin2'] = { - 'name': 'plugin2', - 'versions': ['0.0.2'], - }; - - // Non-federated - final RepositoryPackage plugin1 = - createFakePlugin('plugin1', packagesDir, version: '0.0.2'); - // federated - final RepositoryPackage plugin2 = createFakePlugin( - 'plugin2', packagesDir.childDirectory('plugin2'), - version: '0.0.2'); - - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess( - stdout: '${plugin1.pubspecFile.path}\n' - '${plugin2.pubspecFile.path}\n') - ]; - - Error? commandError; - final List output = await runCapturingPrint(commandRunner, - ['publish', '--all-changed', '--base-sha=HEAD~'], - errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('plugin1 0.0.2 has already been published, ' - 'however the git release tag (plugin1-v0.0.2) was not found.'), - contains('plugin2 0.0.2 has already been published, ' - 'however the git release tag (plugin2-v0.0.2) was not found.'), - ])); - expect( - processRunner.recordedCalls - .map((ProcessCall call) => call.executable), - isNot(contains('git-push'))); - }); - - test('No version change does not release any plugins', () async { - // Non-federated - final RepositoryPackage plugin1 = - createFakePlugin('plugin1', packagesDir); - // federated - final RepositoryPackage plugin2 = - createFakePlugin('plugin2', packagesDir.childDirectory('plugin2')); - - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess( - stdout: '${plugin1.libDirectory.childFile('plugin1.dart').path}\n' - '${plugin2.libDirectory.childFile('plugin2.dart').path}\n') - ]; - - final List output = await runCapturingPrint(commandRunner, - ['publish', '--all-changed', '--base-sha=HEAD~']); - - expect(output, containsAllInOrder(['Ran for 0 package(s)'])); - expect( - processRunner.recordedCalls - .map((ProcessCall call) => call.executable), - isNot(contains('git-push'))); - }); - - test('Do not release flutter_plugin_tools', () async { - mockHttpResponses['plugin1'] = { - 'name': 'flutter_plugin_tools', - 'versions': [], - }; - - final RepositoryPackage flutterPluginTools = - createFakePlugin('flutter_plugin_tools', packagesDir); - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: flutterPluginTools.pubspecFile.path) - ]; - - final List output = await runCapturingPrint(commandRunner, - ['publish', '--all-changed', '--base-sha=HEAD~']); - - expect( - output, - containsAllInOrder([ - contains( - 'SKIPPING: publishing flutter_plugin_tools via the tool is not supported') - ])); - expect( - output.contains( - 'Running `pub publish ` in ${flutterPluginTools.path}...', - ), - isFalse); - expect( - processRunner.recordedCalls - .map((ProcessCall call) => call.executable), - isNot(contains('git-push'))); - }); - }); -} - -/// An extension of [RecordingProcessRunner] that stores 'flutter pub publish' -/// calls so that their input streams can be checked in tests. -class TestProcessRunner extends RecordingProcessRunner { - // Most recent returned publish process. - late MockProcess mockPublishProcess; - - @override - Future start(String executable, List args, - {Directory? workingDirectory}) async { - final io.Process process = - await super.start(executable, args, workingDirectory: workingDirectory); - if (executable == getFlutterCommand(const LocalPlatform()) && - args.isNotEmpty && - args[0] == 'pub' && - args[1] == 'publish') { - mockPublishProcess = process as MockProcess; - } - return process; - } -} - -class MockStdin extends Mock implements io.Stdin { - List> mockUserInputs = >[]; - final StreamController> _controller = StreamController>(); - String? readLineOutput; - - @override - Stream transform(StreamTransformer, S> streamTransformer) { - mockUserInputs.forEach(_addUserInputsToSteam); - return _controller.stream.transform(streamTransformer); - } - - @override - StreamSubscription> listen(void Function(List event)? onData, - {Function? onError, void Function()? onDone, bool? cancelOnError}) { - return _controller.stream.listen(onData, - onError: onError, onDone: onDone, cancelOnError: cancelOnError); - } - - @override - String? readLineSync( - {Encoding encoding = io.systemEncoding, - bool retainNewlines = false}) => - readLineOutput; - - void _addUserInputsToSteam(List input) => _controller.add(input); -} diff --git a/script/tool/test/pubspec_check_command_test.dart b/script/tool/test/pubspec_check_command_test.dart deleted file mode 100644 index 7a9c0cec7cbc..000000000000 --- a/script/tool/test/pubspec_check_command_test.dart +++ /dev/null @@ -1,1143 +0,0 @@ -// Copyright 2013 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. - -import 'package:args/command_runner.dart'; -import 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:flutter_plugin_tools/src/common/core.dart'; -import 'package:flutter_plugin_tools/src/pubspec_check_command.dart'; -import 'package:test/test.dart'; - -import 'mocks.dart'; -import 'util.dart'; - -/// Returns the top section of a pubspec.yaml for a package named [name], -/// for either a flutter/packages or flutter/plugins package depending on -/// the values of [isPlugin]. -/// -/// By default it will create a header that includes all of the expected -/// values, elements can be changed via arguments to create incorrect -/// entries. -/// -/// If [includeRepository] is true, by default the path in the link will -/// be "packages/[name]"; a different "packages"-relative path can be -/// provided with [repositoryPackagesDirRelativePath]. -String _headerSection( - String name, { - bool isPlugin = false, - bool includeRepository = true, - String repositoryBranch = 'main', - String? repositoryPackagesDirRelativePath, - bool includeHomepage = false, - bool includeIssueTracker = true, - bool publishable = true, - String? description, -}) { - final String repositoryPath = repositoryPackagesDirRelativePath ?? name; - final List repoLinkPathComponents = [ - 'flutter', - if (isPlugin) 'plugins' else 'packages', - 'tree', - repositoryBranch, - 'packages', - repositoryPath, - ]; - final String repoLink = - 'https://github.com/${repoLinkPathComponents.join('/')}'; - final String issueTrackerLink = 'https://github.com/flutter/flutter/issues?' - 'q=is%3Aissue+is%3Aopen+label%3A%22p%3A+$name%22'; - description ??= 'A test package for validating that the pubspec.yaml ' - 'follows repo best practices.'; - return ''' -name: $name -description: $description -${includeRepository ? 'repository: $repoLink' : ''} -${includeHomepage ? 'homepage: $repoLink' : ''} -${includeIssueTracker ? 'issue_tracker: $issueTrackerLink' : ''} -version: 1.0.0 -${publishable ? '' : "publish_to: 'none'"} -'''; -} - -String _environmentSection({ - String dartConstraint = '>=2.12.0 <3.0.0', - String? flutterConstraint = '>=2.0.0', -}) { - return [ - 'environment:', - ' sdk: "$dartConstraint"', - if (flutterConstraint != null) ' flutter: "$flutterConstraint"', - '', - ].join('\n'); -} - -String _flutterSection({ - bool isPlugin = false, - String? implementedPackage, - Map> pluginPlatformDetails = - const >{}, -}) { - String pluginEntry = ''' - plugin: -${implementedPackage == null ? '' : ' implements: $implementedPackage'} - platforms: -'''; - - for (final MapEntry> platform - in pluginPlatformDetails.entries) { - pluginEntry += ''' - ${platform.key}: -'''; - for (final MapEntry detail in platform.value.entries) { - pluginEntry += ''' - ${detail.key}: ${detail.value} -'''; - } - } - - return ''' -flutter: -${isPlugin ? pluginEntry : ''} -'''; -} - -String _dependenciesSection() { - return ''' -dependencies: - flutter: - sdk: flutter -'''; -} - -String _devDependenciesSection() { - return ''' -dev_dependencies: - flutter_test: - sdk: flutter -'''; -} - -String _falseSecretsSection() { - return ''' -false_secrets: - - /lib/main.dart -'''; -} - -void main() { - group('test pubspec_check_command', () { - late CommandRunner runner; - late RecordingProcessRunner processRunner; - late FileSystem fileSystem; - late MockPlatform mockPlatform; - late Directory packagesDir; - - setUp(() { - fileSystem = MemoryFileSystem(); - mockPlatform = MockPlatform(); - packagesDir = fileSystem.currentDirectory.childDirectory('packages'); - createPackagesDirectory(parentDir: packagesDir.parent); - processRunner = RecordingProcessRunner(); - final PubspecCheckCommand command = PubspecCheckCommand( - packagesDir, - processRunner: processRunner, - platform: mockPlatform, - ); - - runner = CommandRunner( - 'pubspec_check_command', 'Test for pubspec_check_command'); - runner.addCommand(command); - }); - - test('passes for a plugin following conventions', () async { - final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir); - - plugin.pubspecFile.writeAsStringSync(''' -${_headerSection('plugin', isPlugin: true)} -${_environmentSection()} -${_flutterSection(isPlugin: true)} -${_dependenciesSection()} -${_devDependenciesSection()} -${_falseSecretsSection()} -'''); - - plugin.getExamples().first.pubspecFile.writeAsStringSync(''' -${_headerSection( - 'plugin_example', - publishable: false, - includeRepository: false, - includeIssueTracker: false, - )} -${_environmentSection()} -${_dependenciesSection()} -${_flutterSection()} -'''); - - final List output = await runCapturingPrint(runner, [ - 'pubspec-check', - ]); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin...'), - contains('Running for plugin/example...'), - contains('No issues found!'), - ]), - ); - }); - - test('passes for a Flutter package following conventions', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir); - - package.pubspecFile.writeAsStringSync(''' -${_headerSection('a_package')} -${_environmentSection()} -${_dependenciesSection()} -${_devDependenciesSection()} -${_flutterSection()} -${_falseSecretsSection()} -'''); - - package.getExamples().first.pubspecFile.writeAsStringSync(''' -${_headerSection( - 'a_package', - publishable: false, - includeRepository: false, - includeIssueTracker: false, - )} -${_environmentSection()} -${_dependenciesSection()} -${_flutterSection()} -'''); - - final List output = await runCapturingPrint(runner, [ - 'pubspec-check', - ]); - - expect( - output, - containsAllInOrder([ - contains('Running for a_package...'), - contains('Running for a_package/example...'), - contains('No issues found!'), - ]), - ); - }); - - test('passes for a minimal package following conventions', () async { - final RepositoryPackage package = - createFakePackage('package', packagesDir, examples: []); - - package.pubspecFile.writeAsStringSync(''' -${_headerSection('package')} -${_environmentSection()} -${_dependenciesSection()} -'''); - - final List output = await runCapturingPrint(runner, [ - 'pubspec-check', - ]); - - expect( - output, - containsAllInOrder([ - contains('Running for package...'), - contains('No issues found!'), - ]), - ); - }); - - test('fails when homepage is included', () async { - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, examples: []); - - plugin.pubspecFile.writeAsStringSync(''' -${_headerSection('plugin', isPlugin: true, includeHomepage: true)} -${_environmentSection()} -${_flutterSection(isPlugin: true)} -${_dependenciesSection()} -${_devDependenciesSection()} -'''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['pubspec-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains( - 'Found a "homepage" entry; only "repository" should be used.'), - ]), - ); - }); - - test('fails when repository is missing', () async { - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, examples: []); - - plugin.pubspecFile.writeAsStringSync(''' -${_headerSection('plugin', isPlugin: true, includeRepository: false)} -${_environmentSection()} -${_flutterSection(isPlugin: true)} -${_dependenciesSection()} -${_devDependenciesSection()} -'''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['pubspec-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Missing "repository"'), - ]), - ); - }); - - test('fails when homepage is given instead of repository', () async { - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, examples: []); - - plugin.pubspecFile.writeAsStringSync(''' -${_headerSection('plugin', isPlugin: true, includeHomepage: true, includeRepository: false)} -${_environmentSection()} -${_flutterSection(isPlugin: true)} -${_dependenciesSection()} -${_devDependenciesSection()} -'''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['pubspec-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains( - 'Found a "homepage" entry; only "repository" should be used.'), - ]), - ); - }); - - test('fails when repository package name is incorrect', () async { - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, examples: []); - - plugin.pubspecFile.writeAsStringSync(''' -${_headerSection('plugin', isPlugin: true, repositoryPackagesDirRelativePath: 'different_plugin')} -${_environmentSection()} -${_flutterSection(isPlugin: true)} -${_dependenciesSection()} -${_devDependenciesSection()} -'''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['pubspec-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('The "repository" link should end with the package path.'), - ]), - ); - }); - - test('fails when repository uses master instead of main', () async { - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, examples: []); - - plugin.pubspecFile.writeAsStringSync(''' -${_headerSection('plugin', isPlugin: true, repositoryBranch: 'master')} -${_environmentSection()} -${_flutterSection(isPlugin: true)} -${_dependenciesSection()} -${_devDependenciesSection()} -'''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['pubspec-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('The "repository" link should use "main", not "master".'), - ]), - ); - }); - - test('fails when issue tracker is missing', () async { - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, examples: []); - - plugin.pubspecFile.writeAsStringSync(''' -${_headerSection('plugin', isPlugin: true, includeIssueTracker: false)} -${_environmentSection()} -${_flutterSection(isPlugin: true)} -${_dependenciesSection()} -${_devDependenciesSection()} -'''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['pubspec-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('A package should have an "issue_tracker" link'), - ]), - ); - }); - - test('fails when description is too short', () async { - final RepositoryPackage plugin = createFakePlugin( - 'a_plugin', packagesDir.childDirectory('a_plugin'), - examples: []); - - plugin.pubspecFile.writeAsStringSync(''' -${_headerSection('plugin', isPlugin: true, description: 'Too short')} -${_environmentSection()} -${_flutterSection(isPlugin: true)} -${_dependenciesSection()} -${_devDependenciesSection()} -'''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['pubspec-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('"description" is too short. pub.dev recommends package ' - 'descriptions of 60-180 characters.'), - ]), - ); - }); - - test( - 'allows short descriptions for non-app-facing parts of federated plugins', - () async { - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, examples: []); - - plugin.pubspecFile.writeAsStringSync(''' -${_headerSection('plugin', isPlugin: true, description: 'Too short')} -${_environmentSection()} -${_flutterSection(isPlugin: true)} -${_dependenciesSection()} -${_devDependenciesSection()} -'''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['pubspec-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('"description" is too short. pub.dev recommends package ' - 'descriptions of 60-180 characters.'), - ]), - ); - }); - - test('fails when description is too long', () async { - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, examples: []); - - const String description = 'This description is too long. It just goes ' - 'on and on and on and on and on. pub.dev will down-score it because ' - 'there is just too much here. Someone shoul really cut this down to just ' - 'the core description so that search results are more useful and the ' - 'package does not lose pub points.'; - plugin.pubspecFile.writeAsStringSync(''' -${_headerSection('plugin', isPlugin: true, description: description)} -${_environmentSection()} -${_flutterSection(isPlugin: true)} -${_dependenciesSection()} -${_devDependenciesSection()} -'''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['pubspec-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('"description" is too long. pub.dev recommends package ' - 'descriptions of 60-180 characters.'), - ]), - ); - }); - - test('fails when environment section is out of order', () async { - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, examples: []); - - plugin.pubspecFile.writeAsStringSync(''' -${_headerSection('plugin', isPlugin: true)} -${_flutterSection(isPlugin: true)} -${_dependenciesSection()} -${_devDependenciesSection()} -${_environmentSection()} -'''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['pubspec-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains( - 'Major sections should follow standard repository ordering:'), - ]), - ); - }); - - test('fails when flutter section is out of order', () async { - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, examples: []); - - plugin.pubspecFile.writeAsStringSync(''' -${_headerSection('plugin', isPlugin: true)} -${_flutterSection(isPlugin: true)} -${_environmentSection()} -${_dependenciesSection()} -${_devDependenciesSection()} -'''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['pubspec-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains( - 'Major sections should follow standard repository ordering:'), - ]), - ); - }); - - test('fails when dependencies section is out of order', () async { - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, examples: []); - - plugin.pubspecFile.writeAsStringSync(''' -${_headerSection('plugin', isPlugin: true)} -${_environmentSection()} -${_flutterSection(isPlugin: true)} -${_devDependenciesSection()} -${_dependenciesSection()} -'''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['pubspec-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains( - 'Major sections should follow standard repository ordering:'), - ]), - ); - }); - - test('fails when dev_dependencies section is out of order', () async { - final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir); - - plugin.pubspecFile.writeAsStringSync(''' -${_headerSection('plugin', isPlugin: true)} -${_environmentSection()} -${_devDependenciesSection()} -${_flutterSection(isPlugin: true)} -${_dependenciesSection()} -'''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['pubspec-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains( - 'Major sections should follow standard repository ordering:'), - ]), - ); - }); - - test('fails when false_secrets section is out of order', () async { - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, examples: []); - - plugin.pubspecFile.writeAsStringSync(''' -${_headerSection('plugin', isPlugin: true)} -${_environmentSection()} -${_flutterSection(isPlugin: true)} -${_dependenciesSection()} -${_falseSecretsSection()} -${_devDependenciesSection()} -'''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['pubspec-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains( - 'Major sections should follow standard repository ordering:'), - ]), - ); - }); - - test('fails when an implemenation package is missing "implements"', - () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin_a_foo', packagesDir.childDirectory('plugin_a'), - examples: []); - - plugin.pubspecFile.writeAsStringSync(''' -${_headerSection('plugin_a_foo', isPlugin: true)} -${_environmentSection()} -${_flutterSection(isPlugin: true)} -${_dependenciesSection()} -${_devDependenciesSection()} -'''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['pubspec-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Missing "implements: plugin_a" in "plugin" section.'), - ]), - ); - }); - - test('fails when an implemenation package has the wrong "implements"', - () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin_a_foo', packagesDir.childDirectory('plugin_a'), - examples: []); - - plugin.pubspecFile.writeAsStringSync(''' -${_headerSection('plugin_a_foo', isPlugin: true)} -${_environmentSection()} -${_flutterSection(isPlugin: true, implementedPackage: 'plugin_a_foo')} -${_dependenciesSection()} -${_devDependenciesSection()} -'''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['pubspec-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Expecetd "implements: plugin_a"; ' - 'found "implements: plugin_a_foo".'), - ]), - ); - }); - - test('passes for a correct implemenation package', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin_a_foo', packagesDir.childDirectory('plugin_a'), - examples: []); - - plugin.pubspecFile.writeAsStringSync(''' -${_headerSection( - 'plugin_a_foo', - isPlugin: true, - repositoryPackagesDirRelativePath: 'plugin_a/plugin_a_foo', - )} -${_environmentSection()} -${_flutterSection(isPlugin: true, implementedPackage: 'plugin_a')} -${_dependenciesSection()} -${_devDependenciesSection()} -'''); - - final List output = - await runCapturingPrint(runner, ['pubspec-check']); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin_a_foo...'), - contains('No issues found!'), - ]), - ); - }); - - test('fails when a "default_package" looks incorrect', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin_a', packagesDir.childDirectory('plugin_a'), - examples: []); - - plugin.pubspecFile.writeAsStringSync(''' -${_headerSection( - 'plugin_a', - isPlugin: true, - repositoryPackagesDirRelativePath: 'plugin_a/plugin_a', - )} -${_environmentSection()} -${_flutterSection( - isPlugin: true, - pluginPlatformDetails: >{ - 'android': {'default_package': 'plugin_b_android'} - }, - )} -${_dependenciesSection()} -${_devDependenciesSection()} -'''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['pubspec-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains( - '"plugin_b_android" is not an expected implementation name for "plugin_a"'), - ]), - ); - }); - - test( - 'fails when a "default_package" does not have a corresponding dependency', - () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin_a', packagesDir.childDirectory('plugin_a'), - examples: []); - - plugin.pubspecFile.writeAsStringSync(''' -${_headerSection( - 'plugin_a', - isPlugin: true, - repositoryPackagesDirRelativePath: 'plugin_a/plugin_a', - )} -${_environmentSection()} -${_flutterSection( - isPlugin: true, - pluginPlatformDetails: >{ - 'android': {'default_package': 'plugin_a_android'} - }, - )} -${_dependenciesSection()} -${_devDependenciesSection()} -'''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['pubspec-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('The following default_packages are missing corresponding ' - 'dependencies:\n plugin_a_android'), - ]), - ); - }); - - test('passes for an app-facing package without "implements"', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin_a', packagesDir.childDirectory('plugin_a'), - examples: []); - - plugin.pubspecFile.writeAsStringSync(''' -${_headerSection( - 'plugin_a', - isPlugin: true, - repositoryPackagesDirRelativePath: 'plugin_a/plugin_a', - )} -${_environmentSection()} -${_flutterSection(isPlugin: true)} -${_dependenciesSection()} -${_devDependenciesSection()} -'''); - - final List output = - await runCapturingPrint(runner, ['pubspec-check']); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin_a/plugin_a...'), - contains('No issues found!'), - ]), - ); - }); - - test('passes for a platform interface package without "implements"', - () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin_a_platform_interface', packagesDir.childDirectory('plugin_a'), - examples: []); - - plugin.pubspecFile.writeAsStringSync(''' -${_headerSection( - 'plugin_a_platform_interface', - isPlugin: true, - repositoryPackagesDirRelativePath: - 'plugin_a/plugin_a_platform_interface', - )} -${_environmentSection()} -${_flutterSection(isPlugin: true)} -${_dependenciesSection()} -${_devDependenciesSection()} -'''); - - final List output = - await runCapturingPrint(runner, ['pubspec-check']); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin_a_platform_interface...'), - contains('No issues found!'), - ]), - ); - }); - - test('validates some properties even for unpublished packages', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin_a_foo', packagesDir.childDirectory('plugin_a'), - examples: []); - - // Environment section is in the wrong location. - // Missing 'implements'. - plugin.pubspecFile.writeAsStringSync(''' -${_headerSection('plugin_a_foo', isPlugin: true, publishable: false)} -${_flutterSection(isPlugin: true)} -${_dependenciesSection()} -${_devDependenciesSection()} -${_environmentSection()} -'''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['pubspec-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains( - 'Major sections should follow standard repository ordering:'), - contains('Missing "implements: plugin_a" in "plugin" section.'), - ]), - ); - }); - - test('ignores some checks for unpublished packages', () async { - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, examples: []); - - // Missing metadata that is only useful for published packages, such as - // repository and issue tracker. - plugin.pubspecFile.writeAsStringSync(''' -${_headerSection( - 'plugin', - isPlugin: true, - publishable: false, - includeRepository: false, - includeIssueTracker: false, - )} -${_environmentSection()} -${_flutterSection(isPlugin: true)} -${_dependenciesSection()} -${_devDependenciesSection()} -'''); - - final List output = - await runCapturingPrint(runner, ['pubspec-check']); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin...'), - contains('No issues found!'), - ]), - ); - }); - - test('fails when a Flutter package has a too-low minimum Flutter version', - () async { - final RepositoryPackage package = createFakePackage( - 'a_package', packagesDir, - isFlutter: true, examples: []); - - package.pubspecFile.writeAsStringSync(''' -${_headerSection('a_package')} -${_environmentSection(flutterConstraint: '>=2.10.0')} -${_dependenciesSection()} -'''); - - Error? commandError; - final List output = await runCapturingPrint(runner, [ - 'pubspec-check', - '--min-min-flutter-version', - '3.0.0' - ], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Minimum allowed Flutter version 2.10.0 is less than 3.0.0'), - ]), - ); - }); - - test( - 'passes when a Flutter package requires exactly the minimum Flutter version', - () async { - final RepositoryPackage package = createFakePackage( - 'a_package', packagesDir, - isFlutter: true, examples: []); - - package.pubspecFile.writeAsStringSync(''' -${_headerSection('a_package')} -${_environmentSection(flutterConstraint: '>=3.0.0')} -${_dependenciesSection()} -'''); - - final List output = await runCapturingPrint(runner, - ['pubspec-check', '--min-min-flutter-version', '3.0.0']); - - expect( - output, - containsAllInOrder([ - contains('Running for a_package...'), - contains('No issues found!'), - ]), - ); - }); - - test( - 'passes when a Flutter package requires a higher minimum Flutter version', - () async { - final RepositoryPackage package = createFakePackage( - 'a_package', packagesDir, - isFlutter: true, examples: []); - - package.pubspecFile.writeAsStringSync(''' -${_headerSection('a_package')} -${_environmentSection(flutterConstraint: '>=3.3.0')} -${_dependenciesSection()} -'''); - - final List output = await runCapturingPrint(runner, - ['pubspec-check', '--min-min-flutter-version', '3.0.0']); - - expect( - output, - containsAllInOrder([ - contains('Running for a_package...'), - contains('No issues found!'), - ]), - ); - }); - - test('fails when a non-Flutter package has a too-low minimum Dart version', - () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir, examples: []); - - package.pubspecFile.writeAsStringSync(''' -${_headerSection('a_package')} -${_environmentSection(dartConstraint: '>=2.14.0 <3.0.0', flutterConstraint: null)} -${_dependenciesSection()} -'''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['pubspec-check', '--min-min-dart-version', '2.17.0'], - errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Minimum allowed Dart version 2.14.0 is less than 2.17.0'), - ]), - ); - }); - - test( - 'passes when a non-Flutter package requires exactly the minimum Dart version', - () async { - final RepositoryPackage package = createFakePackage( - 'a_package', packagesDir, - isFlutter: true, examples: []); - - package.pubspecFile.writeAsStringSync(''' -${_headerSection('a_package')} -${_environmentSection(dartConstraint: '>=2.17.0 <3.0.0', flutterConstraint: null)} -${_dependenciesSection()} -'''); - - final List output = await runCapturingPrint(runner, - ['pubspec-check', '--min-min-dart-version', '2.17.0']); - - expect( - output, - containsAllInOrder([ - contains('Running for a_package...'), - contains('No issues found!'), - ]), - ); - }); - - test( - 'passes when a non-Flutter package requires a higher minimum Dart version', - () async { - final RepositoryPackage package = createFakePackage( - 'a_package', packagesDir, - isFlutter: true, examples: []); - - package.pubspecFile.writeAsStringSync(''' -${_headerSection('a_package')} -${_environmentSection(dartConstraint: '>=2.18.0 <3.0.0', flutterConstraint: null)} -${_dependenciesSection()} -'''); - - final List output = await runCapturingPrint(runner, - ['pubspec-check', '--min-min-dart-version', '2.17.0']); - - expect( - output, - containsAllInOrder([ - contains('Running for a_package...'), - contains('No issues found!'), - ]), - ); - }); - }); - - group('test pubspec_check_command on Windows', () { - late CommandRunner runner; - late RecordingProcessRunner processRunner; - late FileSystem fileSystem; - late MockPlatform mockPlatform; - late Directory packagesDir; - - setUp(() { - fileSystem = MemoryFileSystem(style: FileSystemStyle.windows); - mockPlatform = MockPlatform(isWindows: true); - packagesDir = fileSystem.currentDirectory.childDirectory('packages'); - createPackagesDirectory(parentDir: packagesDir.parent); - processRunner = RecordingProcessRunner(); - final PubspecCheckCommand command = PubspecCheckCommand( - packagesDir, - processRunner: processRunner, - platform: mockPlatform, - ); - - runner = CommandRunner( - 'pubspec_check_command', 'Test for pubspec_check_command'); - runner.addCommand(command); - }); - - test('repository check works', () async { - final RepositoryPackage package = - createFakePackage('package', packagesDir, examples: []); - - package.pubspecFile.writeAsStringSync(''' -${_headerSection('package')} -${_environmentSection()} -${_dependenciesSection()} -'''); - - final List output = - await runCapturingPrint(runner, ['pubspec-check']); - - expect( - output, - containsAllInOrder([ - contains('Running for package...'), - contains('No issues found!'), - ]), - ); - }); - }); -} diff --git a/script/tool/test/readme_check_command_test.dart b/script/tool/test/readme_check_command_test.dart deleted file mode 100644 index eb2b6c8e7512..000000000000 --- a/script/tool/test/readme_check_command_test.dart +++ /dev/null @@ -1,741 +0,0 @@ -// Copyright 2013 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. - -import 'package:args/command_runner.dart'; -import 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:flutter_plugin_tools/src/common/core.dart'; -import 'package:flutter_plugin_tools/src/common/plugin_utils.dart'; -import 'package:flutter_plugin_tools/src/readme_check_command.dart'; -import 'package:test/test.dart'; - -import 'mocks.dart'; -import 'util.dart'; - -void main() { - late CommandRunner runner; - late RecordingProcessRunner processRunner; - late FileSystem fileSystem; - late MockPlatform mockPlatform; - late Directory packagesDir; - - setUp(() { - fileSystem = MemoryFileSystem(); - mockPlatform = MockPlatform(); - packagesDir = fileSystem.currentDirectory.childDirectory('packages'); - createPackagesDirectory(parentDir: packagesDir.parent); - processRunner = RecordingProcessRunner(); - final ReadmeCheckCommand command = ReadmeCheckCommand( - packagesDir, - processRunner: processRunner, - platform: mockPlatform, - ); - - runner = CommandRunner( - 'readme_check_command', 'Test for readme_check_command'); - runner.addCommand(command); - }); - - test('prints paths of checked READMEs', () async { - final RepositoryPackage package = createFakePackage( - 'a_package', packagesDir, - examples: ['example1', 'example2']); - for (final RepositoryPackage example in package.getExamples()) { - example.readmeFile.writeAsStringSync('A readme'); - } - getExampleDir(package).childFile('README.md').writeAsStringSync('A readme'); - - final List output = - await runCapturingPrint(runner, ['readme-check']); - - expect( - output, - containsAll([ - contains(' Checking README.md...'), - contains(' Checking example/README.md...'), - contains(' Checking example/example1/README.md...'), - contains(' Checking example/example2/README.md...'), - ]), - ); - }); - - test('fails when package README is missing', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir); - package.readmeFile.deleteSync(); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['readme-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Missing README.md'), - ]), - ); - }); - - test('passes when example README is missing', () async { - createFakePackage('a_package', packagesDir); - - final List output = - await runCapturingPrint(runner, ['readme-check']); - - expect( - output, - containsAllInOrder([ - contains('No README for example'), - ]), - ); - }); - - test('does not inculde non-example subpackages', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir); - const String subpackageName = 'special_test'; - final RepositoryPackage miscSubpackage = - createFakePackage(subpackageName, package.directory); - miscSubpackage.readmeFile.delete(); - - final List output = - await runCapturingPrint(runner, ['readme-check']); - - expect(output, isNot(contains(subpackageName))); - }); - - test('fails when README still has plugin template boilerplate', () async { - final RepositoryPackage package = createFakePlugin('a_plugin', packagesDir); - package.readmeFile.writeAsStringSync(''' -## Getting Started - -This project is a starting point for a Flutter -[plug-in package](https://flutter.dev/developing-packages/), -a specialized package that includes platform-specific implementation code for -Android and/or iOS. - -For help getting started with Flutter development, view the -[online documentation](https://flutter.dev/docs), which offers tutorials, -samples, guidance on mobile development, and a full API reference. -'''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['readme-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('The boilerplate section about getting started with Flutter ' - 'should not be left in.'), - contains('Contains template boilerplate'), - ]), - ); - }); - - test('fails when example README still has application template boilerplate', - () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir); - package.getExamples().first.readmeFile.writeAsStringSync(''' -## Getting Started - -This project is a starting point for a Flutter application. - -A few resources to get you started if this is your first Flutter project: - -- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) -- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) - -For help getting started with Flutter development, view the -[online documentation](https://docs.flutter.dev/), which offers tutorials, -samples, guidance on mobile development, and a full API reference. -'''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['readme-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('The boilerplate section about getting started with Flutter ' - 'should not be left in.'), - contains('Contains template boilerplate'), - ]), - ); - }); - - test( - 'fails when a plugin implementation package example README has the ' - 'template boilerplate', () async { - final RepositoryPackage package = createFakePlugin( - 'a_plugin_ios', packagesDir.childDirectory('a_plugin')); - package.getExamples().first.readmeFile.writeAsStringSync(''' -# a_plugin_ios_example - -Demonstrates how to use the a_plugin_ios plugin. -'''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['readme-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('The boilerplate should not be left in for a federated plugin ' - "implementation package's example."), - contains('Contains template boilerplate'), - ]), - ); - }); - - test( - 'allows the template boilerplate in the example README for packages ' - 'other than plugin implementation packages', () async { - final RepositoryPackage package = createFakePlugin( - 'a_plugin', - packagesDir.childDirectory('a_plugin'), - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline), - }, - ); - // Write a README with an OS support table so that the main README check - // passes. - package.readmeFile.writeAsStringSync(''' -# a_plugin - -| | Android | -|----------------|---------| -| **Support** | SDK 19+ | - -A great plugin. -'''); - package.getExamples().first.readmeFile.writeAsStringSync(''' -# a_plugin_example - -Demonstrates how to use the a_plugin plugin. -'''); - - final List output = - await runCapturingPrint(runner, ['readme-check']); - - expect( - output, - containsAll([ - contains(' Checking README.md...'), - contains(' Checking example/README.md...'), - ]), - ); - }); - - test( - 'fails when a plugin implementation package example README does not have ' - 'the repo-standard message', () async { - final RepositoryPackage package = createFakePlugin( - 'a_plugin_ios', packagesDir.childDirectory('a_plugin')); - package.getExamples().first.readmeFile.writeAsStringSync(''' -# a_plugin_ios_example - -Some random description. -'''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['readme-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('The example README for a platform implementation package ' - 'should warn readers about its intended use. Please copy the ' - 'example README from another implementation package in this ' - 'repository.'), - contains('Missing implementation package example warning'), - ]), - ); - }); - - test('passes for a plugin implementation package with the expected content', - () async { - final RepositoryPackage package = createFakePlugin( - 'a_plugin', - packagesDir.childDirectory('a_plugin'), - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline), - }, - ); - // Write a README with an OS support table so that the main README check - // passes. - package.readmeFile.writeAsStringSync(''' -# a_plugin - -| | Android | -|----------------|---------| -| **Support** | SDK 19+ | - -A great plugin. -'''); - package.getExamples().first.readmeFile.writeAsStringSync(''' -# Platform Implementation Test App - -This is a test app for manual testing and automated integration testing -of this platform implementation. It is not intended to demonstrate actual use of -this package, since the intent is that plugin clients use the app-facing -package. - -Unless you are making changes to this implementation package, this example is -very unlikely to be relevant. -'''); - - final List output = - await runCapturingPrint(runner, ['readme-check']); - - expect( - output, - containsAll([ - contains(' Checking README.md...'), - contains(' Checking example/README.md...'), - ]), - ); - }); - - test( - 'fails when multi-example top-level example directory README still has ' - 'application template boilerplate', () async { - final RepositoryPackage package = createFakePackage( - 'a_package', packagesDir, - examples: ['example1', 'example2']); - package.directory - .childDirectory('example') - .childFile('README.md') - .writeAsStringSync(''' -## Getting Started - -This project is a starting point for a Flutter application. - -A few resources to get you started if this is your first Flutter project: - -- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) -- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) - -For help getting started with Flutter development, view the -[online documentation](https://docs.flutter.dev/), which offers tutorials, -samples, guidance on mobile development, and a full API reference. -'''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['readme-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('The boilerplate section about getting started with Flutter ' - 'should not be left in.'), - contains('Contains template boilerplate'), - ]), - ); - }); - - group('plugin OS support', () { - test( - 'does not check support table for anything other than app-facing plugin packages', - () async { - const String federatedPluginName = 'a_federated_plugin'; - final Directory federatedDir = - packagesDir.childDirectory(federatedPluginName); - // A non-plugin package. - createFakePackage('a_package', packagesDir); - // Non-app-facing parts of a federated plugin. - createFakePlugin( - '${federatedPluginName}_platform_interface', federatedDir); - createFakePlugin('${federatedPluginName}_android', federatedDir); - - final List output = await runCapturingPrint(runner, [ - 'readme-check', - ]); - - expect( - output, - containsAll([ - contains('Running for a_package...'), - contains('Running for a_federated_plugin_platform_interface...'), - contains('Running for a_federated_plugin_android...'), - contains('No issues found!'), - ]), - ); - }); - - test('fails when non-federated plugin is missing an OS support table', - () async { - createFakePlugin('a_plugin', packagesDir); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['readme-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('No OS support table found'), - ]), - ); - }); - - test( - 'fails when app-facing part of a federated plugin is missing an OS support table', - () async { - createFakePlugin('a_plugin', packagesDir.childDirectory('a_plugin')); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['readme-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('No OS support table found'), - ]), - ); - }); - - test('fails the OS support table is missing the header', () async { - final RepositoryPackage plugin = - createFakePlugin('a_plugin', packagesDir); - - plugin.readmeFile.writeAsStringSync(''' -A very useful plugin. - -| **Support** | SDK 21+ | iOS 10+* | [See `camera_web `][1] | -'''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['readme-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('OS support table does not have the expected header format'), - ]), - ); - }); - - test('fails if the OS support table is missing a supported OS', () async { - final RepositoryPackage plugin = createFakePlugin( - 'a_plugin', - packagesDir, - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline), - platformIOS: const PlatformDetails(PlatformSupport.inline), - platformWeb: const PlatformDetails(PlatformSupport.inline), - }, - ); - - plugin.readmeFile.writeAsStringSync(''' -A very useful plugin. - -| | Android | iOS | -|----------------|---------|----------| -| **Support** | SDK 21+ | iOS 10+* | -'''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['readme-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains(' OS support table does not match supported platforms:\n' - ' Actual: android, ios, web\n' - ' Documented: android, ios'), - contains('Incorrect OS support table'), - ]), - ); - }); - - test('fails if the OS support table lists an extra OS', () async { - final RepositoryPackage plugin = createFakePlugin( - 'a_plugin', - packagesDir, - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline), - platformIOS: const PlatformDetails(PlatformSupport.inline), - }, - ); - - plugin.readmeFile.writeAsStringSync(''' -A very useful plugin. - -| | Android | iOS | Web | -|----------------|---------|----------|------------------------| -| **Support** | SDK 21+ | iOS 10+* | [See `camera_web `][1] | -'''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['readme-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains(' OS support table does not match supported platforms:\n' - ' Actual: android, ios\n' - ' Documented: android, ios, web'), - contains('Incorrect OS support table'), - ]), - ); - }); - - test('fails if the OS support table has unexpected OS formatting', - () async { - final RepositoryPackage plugin = createFakePlugin( - 'a_plugin', - packagesDir, - platformSupport: { - platformAndroid: const PlatformDetails(PlatformSupport.inline), - platformIOS: const PlatformDetails(PlatformSupport.inline), - platformMacOS: const PlatformDetails(PlatformSupport.inline), - platformWeb: const PlatformDetails(PlatformSupport.inline), - }, - ); - - plugin.readmeFile.writeAsStringSync(''' -A very useful plugin. - -| | android | ios | MacOS | web | -|----------------|---------|----------|-------|------------------------| -| **Support** | SDK 21+ | iOS 10+* | 10.11 | [See `camera_web `][1] | -'''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['readme-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains(' Incorrect OS capitalization: android, ios, MacOS, web\n' - ' Please use standard capitalizations: Android, iOS, macOS, Web\n'), - contains('Incorrect OS support formatting'), - ]), - ); - }); - }); - - group('code blocks', () { - test('fails on missing info string', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir); - - package.readmeFile.writeAsStringSync(''' -Example: - -``` -void main() { - // ... -} -``` -'''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['readme-check'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Code block at line 3 is missing a language identifier.'), - contains('Missing language identifier for code block'), - ]), - ); - }); - - test('allows unknown info strings', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir); - - package.readmeFile.writeAsStringSync(''' -Example: - -```someunknowninfotag -A B C -``` -'''); - - final List output = await runCapturingPrint(runner, [ - 'readme-check', - ]); - - expect( - output, - containsAll([ - contains('Running for a_package...'), - contains('No issues found!'), - ]), - ); - }); - - test('allows space around info strings', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir); - - package.readmeFile.writeAsStringSync(''' -Example: - -``` dart -A B C -``` -'''); - - final List output = await runCapturingPrint(runner, [ - 'readme-check', - ]); - - expect( - output, - containsAll([ - contains('Running for a_package...'), - contains('No issues found!'), - ]), - ); - }); - - test('passes when excerpt requirement is met', () async { - final RepositoryPackage package = createFakePackage( - 'a_package', - packagesDir, - extraFiles: [kReadmeExcerptConfigPath], - ); - - package.readmeFile.writeAsStringSync(''' -Example: - - -```dart -A B C -``` -'''); - - final List output = await runCapturingPrint( - runner, ['readme-check', '--require-excerpts']); - - expect( - output, - containsAll([ - contains('Running for a_package...'), - contains('No issues found!'), - ]), - ); - }); - - test('fails when excerpts are used but the package is not configured', - () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir); - - package.readmeFile.writeAsStringSync(''' -Example: - - -```dart -A B C -``` -'''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['readme-check', '--require-excerpts'], - errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('code-excerpt tag found, but the package is not configured ' - 'for excerpting. Follow the instructions at\n' - 'https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages\n' - 'for setting up a build.excerpt.yaml file.'), - contains('Missing code-excerpt configuration'), - ]), - ); - }); - - test('fails on missing excerpt tag when requested', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir); - - package.readmeFile.writeAsStringSync(''' -Example: - -```dart -A B C -``` -'''); - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['readme-check', '--require-excerpts'], - errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Dart code block at line 3 is not managed by code-excerpt.'), - // Ensure that the failure message links to instructions. - contains( - 'https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages'), - contains('Missing code-excerpt management for code block'), - ]), - ); - }); - }); -} diff --git a/script/tool/test/remove_dev_dependencies_test.dart b/script/tool/test/remove_dev_dependencies_test.dart deleted file mode 100644 index 776cbf197838..000000000000 --- a/script/tool/test/remove_dev_dependencies_test.dart +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright 2013 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. - -import 'package:args/command_runner.dart'; -import 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:flutter_plugin_tools/src/remove_dev_dependencies.dart'; -import 'package:test/test.dart'; - -import 'util.dart'; - -void main() { - late FileSystem fileSystem; - late Directory packagesDir; - late CommandRunner runner; - - setUp(() { - fileSystem = MemoryFileSystem(); - packagesDir = createPackagesDirectory(fileSystem: fileSystem); - - final RemoveDevDependenciesCommand command = RemoveDevDependenciesCommand( - packagesDir, - ); - runner = CommandRunner('trim_dev_dependencies_command', - 'Test for trim_dev_dependencies_command'); - runner.addCommand(command); - }); - - void addToPubspec(RepositoryPackage package, String addition) { - final String originalContent = package.pubspecFile.readAsStringSync(); - package.pubspecFile.writeAsStringSync(''' -$originalContent -$addition -'''); - } - - test('skips if nothing is removed', () async { - createFakePackage('a_package', packagesDir, version: '1.0.0'); - - final List output = - await runCapturingPrint(runner, ['remove-dev-dependencies']); - - expect( - output, - containsAllInOrder([ - contains('SKIPPING: Nothing to remove.'), - ]), - ); - }); - - test('removes dev_dependencies', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir, version: '1.0.0'); - - addToPubspec(package, ''' -dev_dependencies: - some_dependency: ^2.1.8 - another_dependency: ^1.0.0 -'''); - - final List output = - await runCapturingPrint(runner, ['remove-dev-dependencies']); - - expect( - output, - containsAllInOrder([ - contains('Removed dev_dependencies'), - ]), - ); - expect(package.pubspecFile.readAsStringSync(), - isNot(contains('some_dependency:'))); - expect(package.pubspecFile.readAsStringSync(), - isNot(contains('another_dependency:'))); - }); - - test('removes from examples', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir, version: '1.0.0'); - - final RepositoryPackage example = package.getExamples().first; - addToPubspec(example, ''' -dev_dependencies: - some_dependency: ^2.1.8 - another_dependency: ^1.0.0 -'''); - - final List output = - await runCapturingPrint(runner, ['remove-dev-dependencies']); - - expect( - output, - containsAllInOrder([ - contains('Removed dev_dependencies'), - ]), - ); - expect(package.pubspecFile.readAsStringSync(), - isNot(contains('some_dependency:'))); - expect(package.pubspecFile.readAsStringSync(), - isNot(contains('another_dependency:'))); - }); -} diff --git a/script/tool/test/test_command_test.dart b/script/tool/test/test_command_test.dart deleted file mode 100644 index 14a1e4a67c1f..000000000000 --- a/script/tool/test/test_command_test.dart +++ /dev/null @@ -1,268 +0,0 @@ -// Copyright 2013 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. - -import 'dart:io' as io; - -import 'package:args/command_runner.dart'; -import 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:flutter_plugin_tools/src/common/core.dart'; -import 'package:flutter_plugin_tools/src/common/plugin_utils.dart'; -import 'package:flutter_plugin_tools/src/test_command.dart'; -import 'package:platform/platform.dart'; -import 'package:test/test.dart'; - -import 'mocks.dart'; -import 'util.dart'; - -void main() { - group('$TestCommand', () { - late FileSystem fileSystem; - late Platform mockPlatform; - late Directory packagesDir; - late CommandRunner runner; - late RecordingProcessRunner processRunner; - - setUp(() { - fileSystem = MemoryFileSystem(); - mockPlatform = MockPlatform(); - packagesDir = createPackagesDirectory(fileSystem: fileSystem); - processRunner = RecordingProcessRunner(); - final TestCommand command = TestCommand( - packagesDir, - processRunner: processRunner, - platform: mockPlatform, - ); - - runner = CommandRunner('test_test', 'Test for $TestCommand'); - runner.addCommand(command); - }); - - test('runs flutter test on each plugin', () async { - final RepositoryPackage plugin1 = createFakePlugin('plugin1', packagesDir, - extraFiles: ['test/empty_test.dart']); - final RepositoryPackage plugin2 = createFakePlugin('plugin2', packagesDir, - extraFiles: ['test/empty_test.dart']); - - await runCapturingPrint(runner, ['test']); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall(getFlutterCommand(mockPlatform), - const ['test', '--color'], plugin1.path), - ProcessCall(getFlutterCommand(mockPlatform), - const ['test', '--color'], plugin2.path), - ]), - ); - }); - - test('runs flutter test on Flutter package example tests', () async { - final RepositoryPackage plugin = createFakePlugin('a_plugin', packagesDir, - extraFiles: [ - 'test/empty_test.dart', - 'example/test/an_example_test.dart' - ]); - - await runCapturingPrint(runner, ['test']); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall(getFlutterCommand(mockPlatform), - const ['test', '--color'], plugin.path), - ProcessCall(getFlutterCommand(mockPlatform), - const ['test', '--color'], getExampleDir(plugin).path), - ]), - ); - }); - - test('fails when Flutter tests fail', () async { - createFakePlugin('plugin1', packagesDir, - extraFiles: ['test/empty_test.dart']); - createFakePlugin('plugin2', packagesDir, - extraFiles: ['test/empty_test.dart']); - - processRunner - .mockProcessesForExecutable[getFlutterCommand(mockPlatform)] = - [ - MockProcess(exitCode: 1), // plugin 1 test - MockProcess(), // plugin 2 test - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['test'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('The following packages had errors:'), - contains(' plugin1'), - ])); - }); - - test('skips testing plugins without test directory', () async { - createFakePlugin('plugin1', packagesDir); - final RepositoryPackage plugin2 = createFakePlugin('plugin2', packagesDir, - extraFiles: ['test/empty_test.dart']); - - await runCapturingPrint(runner, ['test']); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall(getFlutterCommand(mockPlatform), - const ['test', '--color'], plugin2.path), - ]), - ); - }); - - test('runs dart run test on non-Flutter packages', () async { - final RepositoryPackage plugin = createFakePlugin('a', packagesDir, - extraFiles: ['test/empty_test.dart']); - final RepositoryPackage package = createFakePackage('b', packagesDir, - extraFiles: ['test/empty_test.dart']); - - await runCapturingPrint( - runner, ['test', '--enable-experiment=exp1']); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - getFlutterCommand(mockPlatform), - const ['test', '--color', '--enable-experiment=exp1'], - plugin.path), - ProcessCall('dart', const ['pub', 'get'], package.path), - ProcessCall( - 'dart', - const ['run', '--enable-experiment=exp1', 'test'], - package.path), - ]), - ); - }); - - test('runs dart run test on non-Flutter package examples', () async { - final RepositoryPackage package = createFakePackage( - 'a_package', packagesDir, extraFiles: [ - 'test/empty_test.dart', - 'example/test/an_example_test.dart' - ]); - - await runCapturingPrint(runner, ['test']); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall('dart', const ['pub', 'get'], package.path), - ProcessCall('dart', const ['run', 'test'], package.path), - ProcessCall('dart', const ['pub', 'get'], - getExampleDir(package).path), - ProcessCall('dart', const ['run', 'test'], - getExampleDir(package).path), - ]), - ); - }); - - test('fails when getting non-Flutter package dependencies fails', () async { - createFakePackage('a_package', packagesDir, - extraFiles: ['test/empty_test.dart']); - - processRunner.mockProcessesForExecutable['dart'] = [ - MockProcess(exitCode: 1), // dart pub get - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['test'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Unable to fetch dependencies'), - contains('The following packages had errors:'), - contains(' a_package'), - ])); - }); - - test('fails when non-Flutter tests fail', () async { - createFakePackage('a_package', packagesDir, - extraFiles: ['test/empty_test.dart']); - - processRunner.mockProcessesForExecutable['dart'] = [ - MockProcess(), // dart pub get - MockProcess(exitCode: 1), // dart pub run test - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['test'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('The following packages had errors:'), - contains(' a_package'), - ])); - }); - - test('runs on Chrome for web plugins', () async { - final RepositoryPackage plugin = createFakePlugin( - 'plugin', - packagesDir, - extraFiles: ['test/empty_test.dart'], - platformSupport: { - platformWeb: const PlatformDetails(PlatformSupport.inline), - }, - ); - - await runCapturingPrint(runner, ['test']); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - getFlutterCommand(mockPlatform), - const ['test', '--color', '--platform=chrome'], - plugin.path), - ]), - ); - }); - - test('enable-experiment flag', () async { - final RepositoryPackage plugin = createFakePlugin('a', packagesDir, - extraFiles: ['test/empty_test.dart']); - final RepositoryPackage package = createFakePackage('b', packagesDir, - extraFiles: ['test/empty_test.dart']); - - await runCapturingPrint( - runner, ['test', '--enable-experiment=exp1']); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - getFlutterCommand(mockPlatform), - const ['test', '--color', '--enable-experiment=exp1'], - plugin.path), - ProcessCall('dart', const ['pub', 'get'], package.path), - ProcessCall( - 'dart', - const ['run', '--enable-experiment=exp1', 'test'], - package.path), - ]), - ); - }); - }); -} diff --git a/script/tool/test/update_excerpts_command_test.dart b/script/tool/test/update_excerpts_command_test.dart deleted file mode 100644 index 5a2f0f340414..000000000000 --- a/script/tool/test/update_excerpts_command_test.dart +++ /dev/null @@ -1,301 +0,0 @@ -// Copyright 2013 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. - -import 'dart:io' as io; - -import 'package:args/command_runner.dart'; -import 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:flutter_plugin_tools/src/common/core.dart'; -import 'package:flutter_plugin_tools/src/update_excerpts_command.dart'; -import 'package:mockito/mockito.dart'; -import 'package:test/test.dart'; - -import 'common/package_command_test.mocks.dart'; -import 'mocks.dart'; -import 'util.dart'; - -void main() { - late FileSystem fileSystem; - late Directory packagesDir; - late RecordingProcessRunner processRunner; - late CommandRunner runner; - - setUp(() { - fileSystem = MemoryFileSystem(); - packagesDir = createPackagesDirectory(fileSystem: fileSystem); - final MockGitDir gitDir = MockGitDir(); - when(gitDir.path).thenReturn(packagesDir.parent.path); - processRunner = RecordingProcessRunner(); - final UpdateExcerptsCommand command = UpdateExcerptsCommand( - packagesDir, - processRunner: processRunner, - platform: MockPlatform(), - gitDir: gitDir, - ); - - runner = CommandRunner( - 'update_excerpts_command', 'Test for update_excerpts_command'); - runner.addCommand(command); - }); - - test('runs pub get before running scripts', () async { - final RepositoryPackage package = createFakePlugin('a_package', packagesDir, - extraFiles: [kReadmeExcerptConfigPath]); - final Directory example = getExampleDir(package); - - await runCapturingPrint(runner, ['update-excerpts']); - - expect( - processRunner.recordedCalls, - containsAll([ - ProcessCall('dart', const ['pub', 'get'], example.path), - ProcessCall( - 'dart', - const [ - 'run', - 'build_runner', - 'build', - '--config', - 'excerpt', - '--output', - 'excerpts', - '--delete-conflicting-outputs', - ], - example.path), - ])); - }); - - test('runs when config is present', () async { - final RepositoryPackage package = createFakePlugin('a_package', packagesDir, - extraFiles: [kReadmeExcerptConfigPath]); - final Directory example = getExampleDir(package); - - final List output = - await runCapturingPrint(runner, ['update-excerpts']); - - expect( - processRunner.recordedCalls, - containsAll([ - ProcessCall( - 'dart', - const [ - 'run', - 'build_runner', - 'build', - '--config', - 'excerpt', - '--output', - 'excerpts', - '--delete-conflicting-outputs', - ], - example.path), - ProcessCall( - 'dart', - const [ - 'run', - 'code_excerpt_updater', - '--write-in-place', - '--yaml', - '--no-escape-ng-interpolation', - '../README.md', - ], - example.path), - ])); - - expect( - output, - containsAllInOrder([ - contains('Ran for 1 package(s)'), - ])); - }); - - test('skips when no config is present', () async { - createFakePlugin('a_package', packagesDir); - - final List output = - await runCapturingPrint(runner, ['update-excerpts']); - - expect(processRunner.recordedCalls, isEmpty); - - expect( - output, - containsAllInOrder([ - contains('Skipped 1 package(s)'), - ])); - }); - - test('restores pubspec even if running the script fails', () async { - final RepositoryPackage package = createFakePlugin('a_package', packagesDir, - extraFiles: [kReadmeExcerptConfigPath]); - - processRunner.mockProcessesForExecutable['dart'] = [ - MockProcess(exitCode: 1), // dart pub get - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['update-excerpts'], errorHandler: (Error e) { - commandError = e; - }); - - // Check that it's definitely a failure in a step between making the changes - // and restoring the original. - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('The following packages had errors:'), - contains('a_package:\n' - ' Unable to get script dependencies') - ])); - - final String examplePubspecContent = - package.getExamples().first.pubspecFile.readAsStringSync(); - expect(examplePubspecContent, isNot(contains('code_excerpter'))); - expect(examplePubspecContent, isNot(contains('code_excerpt_updater'))); - }); - - test('fails if pub get fails', () async { - createFakePlugin('a_package', packagesDir, - extraFiles: [kReadmeExcerptConfigPath]); - - processRunner.mockProcessesForExecutable['dart'] = [ - MockProcess(exitCode: 1), // dart pub get - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['update-excerpts'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('The following packages had errors:'), - contains('a_package:\n' - ' Unable to get script dependencies') - ])); - }); - - test('fails if extraction fails', () async { - createFakePlugin('a_package', packagesDir, - extraFiles: [kReadmeExcerptConfigPath]); - - processRunner.mockProcessesForExecutable['dart'] = [ - MockProcess(), // dart pub get - MockProcess(exitCode: 1), // dart run build_runner ... - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['update-excerpts'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('The following packages had errors:'), - contains('a_package:\n' - ' Unable to extract excerpts') - ])); - }); - - test('fails if injection fails', () async { - createFakePlugin('a_package', packagesDir, - extraFiles: [kReadmeExcerptConfigPath]); - - processRunner.mockProcessesForExecutable['dart'] = [ - MockProcess(), // dart pub get - MockProcess(), // dart run build_runner ... - MockProcess(exitCode: 1), // dart run code_excerpt_updater ... - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['update-excerpts'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('The following packages had errors:'), - contains('a_package:\n' - ' Unable to inject excerpts') - ])); - }); - - test('fails if READMEs are changed with --fail-on-change', () async { - createFakePlugin('a_plugin', packagesDir, - extraFiles: [kReadmeExcerptConfigPath]); - - const String changedFilePath = 'packages/a_plugin/README.md'; - processRunner.mockProcessesForExecutable['git'] = [ - MockProcess(stdout: changedFilePath), - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['update-excerpts', '--fail-on-change'], - errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('README.md is out of sync with its source excerpts'), - contains('Snippets are out of sync in the following files: ' - 'packages/a_plugin/README.md'), - ])); - }); - - test('passes if unrelated files are changed with --fail-on-change', () async { - createFakePlugin('a_plugin', packagesDir, - extraFiles: [kReadmeExcerptConfigPath]); - - const String changedFilePath = 'packages/a_plugin/linux/CMakeLists.txt'; - processRunner.mockProcessesForExecutable['git'] = [ - MockProcess(stdout: changedFilePath), - ]; - - final List output = await runCapturingPrint( - runner, ['update-excerpts', '--fail-on-change']); - - expect( - output, - containsAllInOrder([ - contains('Ran for 1 package(s)'), - ])); - }); - - test('fails if git ls-files fails', () async { - createFakePlugin('a_plugin', packagesDir, - extraFiles: [kReadmeExcerptConfigPath]); - - processRunner.mockProcessesForExecutable['git'] = [ - MockProcess(exitCode: 1) - ]; - Error? commandError; - final List output = await runCapturingPrint( - runner, ['update-excerpts', '--fail-on-change'], - errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Unable to determine local file state'), - ])); - }); -} diff --git a/script/tool/test/update_release_info_command_test.dart b/script/tool/test/update_release_info_command_test.dart deleted file mode 100644 index cfec93823ff0..000000000000 --- a/script/tool/test/update_release_info_command_test.dart +++ /dev/null @@ -1,674 +0,0 @@ -// Copyright 2013 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. - -import 'dart:io' as io; - -import 'package:args/command_runner.dart'; -import 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:flutter_plugin_tools/src/common/core.dart'; -import 'package:flutter_plugin_tools/src/update_release_info_command.dart'; -import 'package:mockito/mockito.dart'; -import 'package:test/test.dart'; - -import 'common/package_command_test.mocks.dart'; -import 'mocks.dart'; -import 'util.dart'; - -void main() { - late FileSystem fileSystem; - late Directory packagesDir; - late MockGitDir gitDir; - late RecordingProcessRunner processRunner; - late CommandRunner runner; - - setUp(() { - fileSystem = MemoryFileSystem(); - packagesDir = createPackagesDirectory(fileSystem: fileSystem); - processRunner = RecordingProcessRunner(); - - gitDir = MockGitDir(); - when(gitDir.path).thenReturn(packagesDir.parent.path); - when(gitDir.runCommand(any, throwOnError: anyNamed('throwOnError'))) - .thenAnswer((Invocation invocation) { - final List arguments = - invocation.positionalArguments[0]! as List; - // Route git calls through a process runner, to make mock output - // consistent with other processes. Attach the first argument to the - // command to make targeting the mock results easier. - final String gitCommand = arguments.removeAt(0); - return processRunner.run('git-$gitCommand', arguments); - }); - - final UpdateReleaseInfoCommand command = UpdateReleaseInfoCommand( - packagesDir, - gitDir: gitDir, - ); - runner = CommandRunner( - 'update_release_info_command', 'Test for update_release_info_command'); - runner.addCommand(command); - }); - - group('flags', () { - test('fails if --changelog is missing', () async { - Exception? commandError; - await runCapturingPrint(runner, [ - 'update-release-info', - '--version=next', - ], exceptionHandler: (Exception e) { - commandError = e; - }); - - expect(commandError, isA()); - }); - - test('fails if --changelog is blank', () async { - Exception? commandError; - await runCapturingPrint(runner, [ - 'update-release-info', - '--version=next', - '--changelog', - '', - ], exceptionHandler: (Exception e) { - commandError = e; - }); - - expect(commandError, isA()); - }); - - test('fails if --version is missing', () async { - Exception? commandError; - await runCapturingPrint( - runner, ['update-release-info', '--changelog', ''], - exceptionHandler: (Exception e) { - commandError = e; - }); - - expect(commandError, isA()); - }); - - test('fails if --version is an unknown value', () async { - Exception? commandError; - await runCapturingPrint(runner, [ - 'update-release-info', - '--version=foo', - '--changelog', - '', - ], exceptionHandler: (Exception e) { - commandError = e; - }); - - expect(commandError, isA()); - }); - }); - - group('changelog', () { - test('adds new NEXT section', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir, version: '1.0.0'); - - const String originalChangelog = ''' -## 1.0.0 - -* Previous changes. -'''; - package.changelogFile.writeAsStringSync(originalChangelog); - - final List output = await runCapturingPrint(runner, [ - 'update-release-info', - '--version=next', - '--changelog', - 'A change.' - ]); - - final String newChangelog = package.changelogFile.readAsStringSync(); - const String expectedChangeLog = ''' -## NEXT - -* A change. - -$originalChangelog'''; - - expect( - output, - containsAllInOrder([ - contains(' Added a NEXT section.'), - ]), - ); - expect(newChangelog, expectedChangeLog); - }); - - test('adds to existing NEXT section', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir, version: '1.0.0'); - - const String originalChangelog = ''' -## NEXT - -* Already-pending changes. - -## 1.0.0 - -* Old changes. -'''; - package.changelogFile.writeAsStringSync(originalChangelog); - - final List output = await runCapturingPrint(runner, [ - 'update-release-info', - '--version=next', - '--changelog', - 'A change.' - ]); - - final String newChangelog = package.changelogFile.readAsStringSync(); - const String expectedChangeLog = ''' -## NEXT - -* A change. -* Already-pending changes. - -## 1.0.0 - -* Old changes. -'''; - - expect(output, - containsAllInOrder([contains(' Updated NEXT section.')])); - expect(newChangelog, expectedChangeLog); - }); - - test('adds new version section', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir, version: '1.0.0'); - - const String originalChangelog = ''' -## 1.0.0 - -* Previous changes. -'''; - package.changelogFile.writeAsStringSync(originalChangelog); - - final List output = await runCapturingPrint(runner, [ - 'update-release-info', - '--version=bugfix', - '--changelog', - 'A change.' - ]); - - final String newChangelog = package.changelogFile.readAsStringSync(); - const String expectedChangeLog = ''' -## 1.0.1 - -* A change. - -$originalChangelog'''; - - expect( - output, - containsAllInOrder([ - contains(' Added a 1.0.1 section.'), - ]), - ); - expect(newChangelog, expectedChangeLog); - }); - - test('converts existing NEXT section to version section', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir, version: '1.0.0'); - - const String originalChangelog = ''' -## NEXT - -* Already-pending changes. - -## 1.0.0 - -* Old changes. -'''; - package.changelogFile.writeAsStringSync(originalChangelog); - - final List output = await runCapturingPrint(runner, [ - 'update-release-info', - '--version=bugfix', - '--changelog', - 'A change.' - ]); - - final String newChangelog = package.changelogFile.readAsStringSync(); - const String expectedChangeLog = ''' -## 1.0.1 - -* A change. -* Already-pending changes. - -## 1.0.0 - -* Old changes. -'''; - - expect(output, - containsAllInOrder([contains(' Updated NEXT section.')])); - expect(newChangelog, expectedChangeLog); - }); - - test('treats multiple lines as multiple list items', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir, version: '1.0.0'); - - const String originalChangelog = ''' -## 1.0.0 - -* Previous changes. -'''; - package.changelogFile.writeAsStringSync(originalChangelog); - - await runCapturingPrint(runner, [ - 'update-release-info', - '--version=bugfix', - '--changelog', - 'First change.\nSecond change.' - ]); - - final String newChangelog = package.changelogFile.readAsStringSync(); - const String expectedChangeLog = ''' -## 1.0.1 - -* First change. -* Second change. - -$originalChangelog'''; - - expect(newChangelog, expectedChangeLog); - }); - - test('adds a period to any lines missing it, and removes whitespace', - () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir, version: '1.0.0'); - - const String originalChangelog = ''' -## 1.0.0 - -* Previous changes. -'''; - package.changelogFile.writeAsStringSync(originalChangelog); - - await runCapturingPrint(runner, [ - 'update-release-info', - '--version=bugfix', - '--changelog', - 'First change \nSecond change' - ]); - - final String newChangelog = package.changelogFile.readAsStringSync(); - const String expectedChangeLog = ''' -## 1.0.1 - -* First change. -* Second change. - -$originalChangelog'''; - - expect(newChangelog, expectedChangeLog); - }); - - test('handles non-standard changelog format', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir, version: '1.0.0'); - - const String originalChangelog = ''' -# 1.0.0 - -* A version with the wrong heading format. -'''; - package.changelogFile.writeAsStringSync(originalChangelog); - - final List output = await runCapturingPrint(runner, [ - 'update-release-info', - '--version=next', - '--changelog', - 'A change.' - ]); - - final String newChangelog = package.changelogFile.readAsStringSync(); - const String expectedChangeLog = ''' -## NEXT - -* A change. - -$originalChangelog'''; - - expect(output, - containsAllInOrder([contains(' Added a NEXT section.')])); - expect(newChangelog, expectedChangeLog); - }); - - test('adds to existing NEXT section using - list style', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir, version: '1.0.0'); - - const String originalChangelog = ''' -## NEXT - - - Already-pending changes. - -## 1.0.0 - - - Previous changes. -'''; - package.changelogFile.writeAsStringSync(originalChangelog); - - final List output = await runCapturingPrint(runner, [ - 'update-release-info', - '--version=next', - '--changelog', - 'A change.' - ]); - - final String newChangelog = package.changelogFile.readAsStringSync(); - const String expectedChangeLog = ''' -## NEXT - - - A change. - - Already-pending changes. - -## 1.0.0 - - - Previous changes. -'''; - - expect(output, - containsAllInOrder([contains(' Updated NEXT section.')])); - expect(newChangelog, expectedChangeLog); - }); - - test('skips for "minimal" when there are no changes at all', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir, version: '1.0.1'); - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: ''' -packages/different_package/lib/foo.dart -'''), - ]; - final String originalChangelog = package.changelogFile.readAsStringSync(); - - final List output = await runCapturingPrint(runner, [ - 'update-release-info', - '--version=minimal', - '--changelog', - 'A change.', - ]); - - final String version = package.parsePubspec().version?.toString() ?? ''; - expect(version, '1.0.1'); - expect(package.changelogFile.readAsStringSync(), originalChangelog); - expect( - output, - containsAllInOrder([ - contains('No changes to package'), - contains('Skipped 1 package') - ])); - }); - - test('skips for "minimal" when there are only test changes', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir, version: '1.0.1'); - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: ''' -packages/a_package/test/a_test.dart -packages/a_package/example/integration_test/another_test.dart -'''), - ]; - final String originalChangelog = package.changelogFile.readAsStringSync(); - - final List output = await runCapturingPrint(runner, [ - 'update-release-info', - '--version=minimal', - '--changelog', - 'A change.', - ]); - - final String version = package.parsePubspec().version?.toString() ?? ''; - expect(version, '1.0.1'); - expect(package.changelogFile.readAsStringSync(), originalChangelog); - expect( - output, - containsAllInOrder([ - contains('No non-exempt changes to package'), - contains('Skipped 1 package') - ])); - }); - - test('fails if CHANGELOG.md is missing', () async { - createFakePackage('a_package', packagesDir, includeCommonFiles: false); - - Error? commandError; - final List output = await runCapturingPrint(runner, [ - 'update-release-info', - '--version=minor', - '--changelog', - 'A change.', - ], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect(output, - containsAllInOrder([contains(' Missing CHANGELOG.md.')])); - }); - - test('fails if CHANGELOG.md has unexpected NEXT block format', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir, version: '1.0.0'); - - const String originalChangelog = ''' -## NEXT - -Some free-form text that isn't a list. - -## 1.0.0 - -- Previous changes. -'''; - package.changelogFile.writeAsStringSync(originalChangelog); - - Error? commandError; - final List output = await runCapturingPrint(runner, [ - 'update-release-info', - '--version=minor', - '--changelog', - 'A change.', - ], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains(' Existing NEXT section has unrecognized format.') - ])); - }); - }); - - group('pubspec', () { - test('does not change for --next', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir, version: '1.0.0'); - - await runCapturingPrint(runner, [ - 'update-release-info', - '--version=next', - '--changelog', - 'A change.' - ]); - - final String version = package.parsePubspec().version?.toString() ?? ''; - expect(version, '1.0.0'); - }); - - test('updates bugfix version for pre-1.0 without existing build number', - () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir, version: '0.1.0'); - - final List output = await runCapturingPrint(runner, [ - 'update-release-info', - '--version=bugfix', - '--changelog', - 'A change.', - ]); - - final String version = package.parsePubspec().version?.toString() ?? ''; - expect(version, '0.1.0+1'); - expect( - output, - containsAllInOrder( - [contains(' Incremented version to 0.1.0+1')])); - }); - - test('updates bugfix version for pre-1.0 with existing build number', - () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir, version: '0.1.0+2'); - - final List output = await runCapturingPrint(runner, [ - 'update-release-info', - '--version=bugfix', - '--changelog', - 'A change.', - ]); - - final String version = package.parsePubspec().version?.toString() ?? ''; - expect(version, '0.1.0+3'); - expect( - output, - containsAllInOrder( - [contains(' Incremented version to 0.1.0+3')])); - }); - - test('updates bugfix version for post-1.0', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir, version: '1.0.1'); - - final List output = await runCapturingPrint(runner, [ - 'update-release-info', - '--version=bugfix', - '--changelog', - 'A change.', - ]); - - final String version = package.parsePubspec().version?.toString() ?? ''; - expect(version, '1.0.2'); - expect( - output, - containsAllInOrder( - [contains(' Incremented version to 1.0.2')])); - }); - - test('updates minor version for pre-1.0', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir, version: '0.1.0+2'); - - final List output = await runCapturingPrint(runner, [ - 'update-release-info', - '--version=minor', - '--changelog', - 'A change.', - ]); - - final String version = package.parsePubspec().version?.toString() ?? ''; - expect(version, '0.1.1'); - expect( - output, - containsAllInOrder( - [contains(' Incremented version to 0.1.1')])); - }); - - test('updates minor version for post-1.0', () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir, version: '1.0.1'); - - final List output = await runCapturingPrint(runner, [ - 'update-release-info', - '--version=minor', - '--changelog', - 'A change.', - ]); - - final String version = package.parsePubspec().version?.toString() ?? ''; - expect(version, '1.1.0'); - expect( - output, - containsAllInOrder( - [contains(' Incremented version to 1.1.0')])); - }); - - test('updates bugfix version for "minimal" with publish-worthy changes', - () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir, version: '1.0.1'); - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: ''' -packages/a_package/lib/plugin.dart -'''), - ]; - - final List output = await runCapturingPrint(runner, [ - 'update-release-info', - '--version=minimal', - '--changelog', - 'A change.', - ]); - - final String version = package.parsePubspec().version?.toString() ?? ''; - expect(version, '1.0.2'); - expect( - output, - containsAllInOrder( - [contains(' Incremented version to 1.0.2')])); - }); - - test('no version change for "minimal" with non-publish-worthy changes', - () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir, version: '1.0.1'); - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: ''' -packages/a_package/test/plugin_test.dart -'''), - ]; - - await runCapturingPrint(runner, [ - 'update-release-info', - '--version=minimal', - '--changelog', - 'A change.', - ]); - - final String version = package.parsePubspec().version?.toString() ?? ''; - expect(version, '1.0.1'); - }); - - test('fails if there is no version in pubspec', () async { - createFakePackage('a_package', packagesDir, version: null); - - Error? commandError; - final List output = await runCapturingPrint(runner, [ - 'update-release-info', - '--version=minor', - '--changelog', - 'A change.', - ], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder( - [contains('Could not determine current version.')])); - }); - }); -} diff --git a/script/tool/test/util.dart b/script/tool/test/util.dart deleted file mode 100644 index 913242b6ea69..000000000000 --- a/script/tool/test/util.dart +++ /dev/null @@ -1,471 +0,0 @@ -// Copyright 2013 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. - -import 'dart:async'; -import 'dart:convert'; -import 'dart:io' as io; - -import 'package:args/command_runner.dart'; -import 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:flutter_plugin_tools/src/common/core.dart'; -import 'package:flutter_plugin_tools/src/common/file_utils.dart'; -import 'package:flutter_plugin_tools/src/common/plugin_utils.dart'; -import 'package:flutter_plugin_tools/src/common/process_runner.dart'; -import 'package:flutter_plugin_tools/src/common/repository_package.dart'; -import 'package:meta/meta.dart'; -import 'package:path/path.dart' as p; -import 'package:platform/platform.dart'; -import 'package:quiver/collection.dart'; - -import 'mocks.dart'; - -export 'package:flutter_plugin_tools/src/common/repository_package.dart'; - -/// The relative path from a package to the file that is used to enable -/// README excerpting for a package. -// This is a shared constant to ensure that both readme-check and -// update-excerpt are looking for the same file, so that readme-check can't -// get out of sync with what actually drives excerpting. -const String kReadmeExcerptConfigPath = 'example/build.excerpt.yaml'; - -const String _defaultDartConstraint = '>=2.14.0 <3.0.0'; -const String _defaultFlutterConstraint = '>=2.5.0'; - -/// Returns the exe name that command will use when running Flutter on -/// [platform]. -String getFlutterCommand(Platform platform) => - platform.isWindows ? 'flutter.bat' : 'flutter'; - -/// Creates a packages directory in the given location. -/// -/// If [parentDir] is set the packages directory will be created there, -/// otherwise [fileSystem] must be provided and it will be created an arbitrary -/// location in that filesystem. -Directory createPackagesDirectory( - {Directory? parentDir, FileSystem? fileSystem}) { - assert(parentDir != null || fileSystem != null, - 'One of parentDir or fileSystem must be provided'); - assert(fileSystem == null || fileSystem is MemoryFileSystem, - 'If using a real filesystem, parentDir must be provided'); - final Directory packagesDir = - (parentDir ?? fileSystem!.currentDirectory).childDirectory('packages'); - packagesDir.createSync(); - return packagesDir; -} - -/// Details for platform support in a plugin. -@immutable -class PlatformDetails { - const PlatformDetails( - this.type, { - this.hasNativeCode = true, - this.hasDartCode = false, - }); - - /// The type of support for the platform. - final PlatformSupport type; - - /// Whether or not the plugin includes native code. - /// - /// Ignored for web, which does not have native code. - final bool hasNativeCode; - - /// Whether or not the plugin includes Dart code. - /// - /// Ignored for web, which always has native code. - final bool hasDartCode; -} - -/// Returns the 'example' directory for [package]. -/// -/// This is deliberately not a method on [RepositoryPackage] since actual tool -/// code should essentially never need this, and instead be using -/// [RepositoryPackage.getExamples] to avoid assuming there's a single example -/// directory. However, needing to construct paths with the example directory -/// is very common in test code. -/// -/// This returns a Directory rather than a RepositoryPackage because there is no -/// guarantee that the returned directory is a package. -Directory getExampleDir(RepositoryPackage package) { - return package.directory.childDirectory('example'); -} - -/// Creates a plugin package with the given [name] in [packagesDirectory]. -/// -/// [platformSupport] is a map of platform string to the support details for -/// that platform. -/// -/// [extraFiles] is an optional list of plugin-relative paths, using Posix -/// separators, of extra files to create in the plugin. -RepositoryPackage createFakePlugin( - String name, - Directory parentDirectory, { - List examples = const ['example'], - List extraFiles = const [], - Map platformSupport = - const {}, - String? version = '0.0.1', - String flutterConstraint = _defaultFlutterConstraint, - String dartConstraint = _defaultDartConstraint, -}) { - final RepositoryPackage package = createFakePackage( - name, - parentDirectory, - isFlutter: true, - examples: examples, - extraFiles: extraFiles, - version: version, - flutterConstraint: flutterConstraint, - dartConstraint: dartConstraint, - ); - - createFakePubspec( - package, - name: name, - isPlugin: true, - platformSupport: platformSupport, - version: version, - flutterConstraint: flutterConstraint, - dartConstraint: dartConstraint, - ); - - return package; -} - -/// Creates a plugin package with the given [name] in [packagesDirectory]. -/// -/// [extraFiles] is an optional list of package-relative paths, using unix-style -/// separators, of extra files to create in the package. -/// -/// If [includeCommonFiles] is true, common but non-critical files like -/// CHANGELOG.md, README.md, and AUTHORS will be included. -/// -/// If non-null, [directoryName] will be used for the directory instead of -/// [name]. -RepositoryPackage createFakePackage( - String name, - Directory parentDirectory, { - List examples = const ['example'], - List extraFiles = const [], - bool isFlutter = false, - String? version = '0.0.1', - String flutterConstraint = _defaultFlutterConstraint, - String dartConstraint = _defaultDartConstraint, - bool includeCommonFiles = true, - String? directoryName, - String? publishTo, -}) { - final RepositoryPackage package = - RepositoryPackage(parentDirectory.childDirectory(directoryName ?? name)); - package.directory.createSync(recursive: true); - - package.libDirectory.createSync(); - createFakePubspec(package, - name: name, - isFlutter: isFlutter, - version: version, - flutterConstraint: flutterConstraint, - dartConstraint: dartConstraint); - if (includeCommonFiles) { - package.changelogFile.writeAsStringSync(''' -## $version - * Some changes. - '''); - package.readmeFile.writeAsStringSync('A very useful package'); - package.authorsFile.writeAsStringSync('Google Inc.'); - } - - if (examples.length == 1) { - createFakePackage('${name}_example', package.directory, - directoryName: examples.first, - examples: [], - includeCommonFiles: false, - isFlutter: isFlutter, - publishTo: 'none', - flutterConstraint: flutterConstraint, - dartConstraint: dartConstraint); - } else if (examples.isNotEmpty) { - final Directory examplesDirectory = getExampleDir(package)..createSync(); - for (final String exampleName in examples) { - createFakePackage(exampleName, examplesDirectory, - examples: [], - includeCommonFiles: false, - isFlutter: isFlutter, - publishTo: 'none', - flutterConstraint: flutterConstraint, - dartConstraint: dartConstraint); - } - } - - final p.Context posixContext = p.posix; - for (final String file in extraFiles) { - childFileWithSubcomponents(package.directory, posixContext.split(file)) - .createSync(recursive: true); - } - - return package; -} - -/// Creates a `pubspec.yaml` file for [package]. -/// -/// [platformSupport] is a map of platform string to the support details for -/// that platform. If empty, no `plugin` entry will be created unless `isPlugin` -/// is set to `true`. -void createFakePubspec( - RepositoryPackage package, { - String name = 'fake_package', - bool isFlutter = true, - bool isPlugin = false, - Map platformSupport = - const {}, - String? publishTo, - String? version, - String dartConstraint = _defaultDartConstraint, - String flutterConstraint = _defaultFlutterConstraint, -}) { - isPlugin |= platformSupport.isNotEmpty; - - String environmentSection = ''' -environment: - sdk: "$dartConstraint" -'''; - String dependenciesSection = ''' -dependencies: -'''; - String pluginSection = ''; - - // Add Flutter-specific entries if requested. - if (isFlutter) { - environmentSection += ''' - flutter: "$flutterConstraint" -'''; - dependenciesSection += ''' - flutter: - sdk: flutter -'''; - - if (isPlugin) { - pluginSection += ''' -flutter: - plugin: - platforms: -'''; - for (final MapEntry platform - in platformSupport.entries) { - pluginSection += - _pluginPlatformSection(platform.key, platform.value, name); - } - } - } - - // Default to a fake server to avoid ever accidentally publishing something - // from a test. Does not use 'none' since that changes the behavior of some - // commands. - final String publishToSection = - 'publish_to: ${publishTo ?? 'http://no_pub_server.com'}'; - - final String yaml = ''' -name: $name -${(version != null) ? 'version: $version' : ''} -$publishToSection - -$environmentSection - -$dependenciesSection - -$pluginSection -'''; - - package.pubspecFile.createSync(); - package.pubspecFile.writeAsStringSync(yaml); -} - -String _pluginPlatformSection( - String platform, PlatformDetails support, String packageName) { - String entry = ''; - // Build the main plugin entry. - if (support.type == PlatformSupport.federated) { - entry = ''' - $platform: - default_package: ${packageName}_$platform -'''; - } else { - final List lines = [ - ' $platform:', - ]; - switch (platform) { - case platformAndroid: - lines.add(' package: io.flutter.plugins.fake'); - continue nativeByDefault; - nativeByDefault: - case platformIOS: - case platformLinux: - case platformMacOS: - case platformWindows: - if (support.hasNativeCode) { - final String className = - platform == platformIOS ? 'FLTFakePlugin' : 'FakePlugin'; - lines.add(' pluginClass: $className'); - } - if (support.hasDartCode) { - lines.add(' dartPluginClass: FakeDartPlugin'); - } - break; - case platformWeb: - lines.addAll([ - ' pluginClass: FakePlugin', - ' fileName: ${packageName}_web.dart', - ]); - break; - default: - assert(false, 'Unrecognized platform: $platform'); - break; - } - entry = '${lines.join('\n')}\n'; - } - - return entry; -} - -/// Run the command [runner] with the given [args] and return -/// what was printed. -/// A custom [errorHandler] can be used to handle the runner error as desired without throwing. -Future> runCapturingPrint( - CommandRunner runner, - List args, { - Function(Error error)? errorHandler, - Function(Exception error)? exceptionHandler, -}) async { - final List prints = []; - final ZoneSpecification spec = ZoneSpecification( - print: (_, __, ___, String message) { - prints.add(message); - }, - ); - try { - await Zone.current - .fork(specification: spec) - .run>(() => runner.run(args)); - } on Error catch (e) { - if (errorHandler == null) { - rethrow; - } - errorHandler(e); - } on Exception catch (e) { - if (exceptionHandler == null) { - rethrow; - } - exceptionHandler(e); - } - - return prints; -} - -/// A mock [ProcessRunner] which records process calls. -class RecordingProcessRunner extends ProcessRunner { - final List recordedCalls = []; - - /// Maps an executable to a list of processes that should be used for each - /// successive call to it via [run], [runAndStream], or [start]. - final Map> mockProcessesForExecutable = - >{}; - - @override - Future runAndStream( - String executable, - List args, { - Directory? workingDir, - bool exitOnError = false, - }) async { - recordedCalls.add(ProcessCall(executable, args, workingDir?.path)); - final io.Process? processToReturn = _getProcessToReturn(executable); - final int exitCode = - processToReturn == null ? 0 : await processToReturn.exitCode; - if (exitOnError && (exitCode != 0)) { - throw io.ProcessException(executable, args); - } - return Future.value(exitCode); - } - - /// Returns [io.ProcessResult] created from [mockProcessesForExecutable]. - @override - Future run( - String executable, - List args, { - Directory? workingDir, - bool exitOnError = false, - bool logOnError = false, - Encoding stdoutEncoding = io.systemEncoding, - Encoding stderrEncoding = io.systemEncoding, - }) async { - recordedCalls.add(ProcessCall(executable, args, workingDir?.path)); - - final io.Process? process = _getProcessToReturn(executable); - final List? processStdout = - await process?.stdout.transform(stdoutEncoding.decoder).toList(); - final String stdout = processStdout?.join() ?? ''; - final List? processStderr = - await process?.stderr.transform(stderrEncoding.decoder).toList(); - final String stderr = processStderr?.join() ?? ''; - - final io.ProcessResult result = process == null - ? io.ProcessResult(1, 0, '', '') - : io.ProcessResult(process.pid, await process.exitCode, stdout, stderr); - - if (exitOnError && (result.exitCode != 0)) { - throw io.ProcessException(executable, args); - } - - return Future.value(result); - } - - @override - Future start(String executable, List args, - {Directory? workingDirectory}) async { - recordedCalls.add(ProcessCall(executable, args, workingDirectory?.path)); - return Future.value( - _getProcessToReturn(executable) ?? MockProcess()); - } - - io.Process? _getProcessToReturn(String executable) { - final List? processes = mockProcessesForExecutable[executable]; - if (processes != null && processes.isNotEmpty) { - return processes.removeAt(0); - } - return null; - } -} - -/// A recorded process call. -@immutable -class ProcessCall { - const ProcessCall(this.executable, this.args, this.workingDir); - - /// The executable that was called. - final String executable; - - /// The arguments passed to [executable] in the call. - final List args; - - /// The working directory this process was called from. - final String? workingDir; - - @override - bool operator ==(Object other) { - return other is ProcessCall && - executable == other.executable && - listsEqual(args, other.args) && - workingDir == other.workingDir; - } - - @override - int get hashCode => Object.hash(executable, args, workingDir); - - @override - String toString() { - final List command = [executable, ...args]; - return '"${command.join(' ')}" in $workingDir'; - } -} diff --git a/script/tool/test/version_check_command_test.dart b/script/tool/test/version_check_command_test.dart deleted file mode 100644 index d485d81ceaf2..000000000000 --- a/script/tool/test/version_check_command_test.dart +++ /dev/null @@ -1,1468 +0,0 @@ -// Copyright 2013 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. - -import 'dart:convert'; -import 'dart:io' as io; - -import 'package:args/command_runner.dart'; -import 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:flutter_plugin_tools/src/common/core.dart'; -import 'package:flutter_plugin_tools/src/version_check_command.dart'; -import 'package:http/http.dart' as http; -import 'package:http/testing.dart'; -import 'package:mockito/mockito.dart'; -import 'package:pub_semver/pub_semver.dart'; -import 'package:test/test.dart'; - -import 'common/package_command_test.mocks.dart'; -import 'mocks.dart'; -import 'util.dart'; - -void testAllowedVersion( - String mainVersion, - String headVersion, { - bool allowed = true, - NextVersionType? nextVersionType, -}) { - final Version main = Version.parse(mainVersion); - final Version head = Version.parse(headVersion); - final Map allowedVersions = - getAllowedNextVersions(main, newVersion: head); - if (allowed) { - expect(allowedVersions, contains(head)); - if (nextVersionType != null) { - expect(allowedVersions[head], equals(nextVersionType)); - } - } else { - expect(allowedVersions, isNot(contains(head))); - } -} - -class MockProcessResult extends Mock implements io.ProcessResult {} - -void main() { - const String indentation = ' '; - group('VersionCheckCommand', () { - late FileSystem fileSystem; - late MockPlatform mockPlatform; - late Directory packagesDir; - late CommandRunner runner; - late RecordingProcessRunner processRunner; - late MockGitDir gitDir; - // Ignored if mockHttpResponse is set. - int mockHttpStatus; - Map? mockHttpResponse; - - setUp(() { - fileSystem = MemoryFileSystem(); - mockPlatform = MockPlatform(); - packagesDir = createPackagesDirectory(fileSystem: fileSystem); - - gitDir = MockGitDir(); - when(gitDir.path).thenReturn(packagesDir.parent.path); - when(gitDir.runCommand(any, throwOnError: anyNamed('throwOnError'))) - .thenAnswer((Invocation invocation) { - final List arguments = - invocation.positionalArguments[0]! as List; - // Route git calls through the process runner, to make mock output - // consistent with other processes. Attach the first argument to the - // command to make targeting the mock results easier. - final String gitCommand = arguments.removeAt(0); - return processRunner.run('git-$gitCommand', arguments); - }); - - // Default to simulating the plugin never having been published. - mockHttpStatus = 404; - mockHttpResponse = null; - final MockClient mockClient = MockClient((http.Request request) async { - return http.Response(json.encode(mockHttpResponse), - mockHttpResponse == null ? mockHttpStatus : 200); - }); - - processRunner = RecordingProcessRunner(); - final VersionCheckCommand command = VersionCheckCommand(packagesDir, - processRunner: processRunner, - platform: mockPlatform, - gitDir: gitDir, - httpClient: mockClient); - - runner = CommandRunner( - 'version_check_command', 'Test for $VersionCheckCommand'); - runner.addCommand(command); - }); - - test('allows valid version', () async { - createFakePlugin('plugin', packagesDir, version: '2.0.0'); - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: 'version: 1.0.0'), - ]; - final List output = await runCapturingPrint( - runner, ['version-check', '--base-sha=main']); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('1.0.0 -> 2.0.0'), - ]), - ); - expect( - processRunner.recordedCalls, - containsAllInOrder(const [ - ProcessCall( - 'git-show', ['main:packages/plugin/pubspec.yaml'], null) - ])); - }); - - test('denies invalid version', () async { - createFakePlugin('plugin', packagesDir, version: '0.2.0'); - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: 'version: 0.0.1'), - ]; - Error? commandError; - final List output = await runCapturingPrint( - runner, ['version-check', '--base-sha=main'], - errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Incorrectly updated version.'), - ])); - expect( - processRunner.recordedCalls, - containsAllInOrder(const [ - ProcessCall( - 'git-show', ['main:packages/plugin/pubspec.yaml'], null) - ])); - }); - - test('uses merge-base without explicit base-sha', () async { - createFakePlugin('plugin', packagesDir, version: '2.0.0'); - processRunner.mockProcessesForExecutable['git-merge-base'] = [ - MockProcess(stdout: 'abc123'), - MockProcess(stdout: 'abc123'), - ]; - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: 'version: 1.0.0'), - ]; - final List output = - await runCapturingPrint(runner, ['version-check']); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('1.0.0 -> 2.0.0'), - ]), - ); - expect( - processRunner.recordedCalls, - containsAllInOrder(const [ - ProcessCall('git-merge-base', - ['--fork-point', 'FETCH_HEAD', 'HEAD'], null), - ProcessCall('git-show', - ['abc123:packages/plugin/pubspec.yaml'], null), - ])); - }); - - test('allows valid version for new package.', () async { - createFakePlugin('plugin', packagesDir, version: '1.0.0'); - final List output = - await runCapturingPrint(runner, ['version-check']); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('Unable to find previous version at git base.'), - ]), - ); - }); - - test('allows likely reverts.', () async { - createFakePlugin('plugin', packagesDir, version: '0.6.1'); - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: 'version: 0.6.2'), - ]; - final List output = await runCapturingPrint( - runner, ['version-check', '--base-sha=main']); - - expect( - output, - containsAllInOrder([ - contains('New version is lower than previous version. ' - 'This is assumed to be a revert.'), - ]), - ); - expect( - processRunner.recordedCalls, - containsAllInOrder(const [ - ProcessCall( - 'git-show', ['main:packages/plugin/pubspec.yaml'], null) - ])); - }); - - test('denies lower version that could not be a simple revert', () async { - createFakePlugin('plugin', packagesDir, version: '0.5.1'); - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: 'version: 0.6.2'), - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['version-check', '--base-sha=main'], - errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Incorrectly updated version.'), - ])); - expect( - processRunner.recordedCalls, - containsAllInOrder(const [ - ProcessCall( - 'git-show', ['main:packages/plugin/pubspec.yaml'], null) - ])); - }); - - test('allows minor changes to platform interfaces', () async { - createFakePlugin('plugin_platform_interface', packagesDir, - version: '1.1.0'); - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: 'version: 1.0.0'), - ]; - final List output = await runCapturingPrint( - runner, ['version-check', '--base-sha=main']); - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('1.0.0 -> 1.1.0'), - ]), - ); - expect( - processRunner.recordedCalls, - containsAllInOrder(const [ - ProcessCall( - 'git-show', - [ - 'main:packages/plugin_platform_interface/pubspec.yaml' - ], - null) - ])); - }); - - test('disallows breaking changes to platform interfaces by default', - () async { - createFakePlugin('plugin_platform_interface', packagesDir, - version: '2.0.0'); - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: 'version: 1.0.0'), - ]; - Error? commandError; - final List output = await runCapturingPrint( - runner, ['version-check', '--base-sha=main'], - errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains( - ' Breaking changes to platform interfaces are not allowed ' - 'without explicit justification.\n' - ' See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages ' - 'for more information.'), - ])); - expect( - processRunner.recordedCalls, - containsAllInOrder(const [ - ProcessCall( - 'git-show', - [ - 'main:packages/plugin_platform_interface/pubspec.yaml' - ], - null) - ])); - }); - - test('allows breaking changes to platform interfaces with override label', - () async { - createFakePlugin('plugin_platform_interface', packagesDir, - version: '2.0.0'); - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: 'version: 1.0.0'), - ]; - - final List output = await runCapturingPrint(runner, [ - 'version-check', - '--base-sha=main', - '--pr-labels=some label,override: allow breaking change,another-label' - ]); - - expect( - output, - containsAllInOrder([ - contains('Allowing breaking change to plugin_platform_interface ' - 'due to the "override: allow breaking change" label.'), - contains('Ran for 1 package(s) (1 with warnings)'), - ]), - ); - expect( - processRunner.recordedCalls, - containsAllInOrder(const [ - ProcessCall( - 'git-show', - [ - 'main:packages/plugin_platform_interface/pubspec.yaml' - ], - null) - ])); - }); - - test('allows breaking changes to platform interfaces with bypass flag', - () async { - createFakePlugin('plugin_platform_interface', packagesDir, - version: '2.0.0'); - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: 'version: 1.0.0'), - ]; - final List output = await runCapturingPrint(runner, [ - 'version-check', - '--base-sha=main', - '--ignore-platform-interface-breaks' - ]); - - expect( - output, - containsAllInOrder([ - contains('Allowing breaking change to plugin_platform_interface due ' - 'to --ignore-platform-interface-breaks'), - contains('Ran for 1 package(s) (1 with warnings)'), - ]), - ); - expect( - processRunner.recordedCalls, - containsAllInOrder(const [ - ProcessCall( - 'git-show', - [ - 'main:packages/plugin_platform_interface/pubspec.yaml' - ], - null) - ])); - }); - - test('Allow empty lines in front of the first version in CHANGELOG', - () async { - const String version = '1.0.1'; - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, version: version); - const String changelog = ''' - -## $version -* Some changes. -'''; - plugin.changelogFile.writeAsStringSync(changelog); - final List output = await runCapturingPrint( - runner, ['version-check', '--base-sha=main']); - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - ]), - ); - }); - - test('Throws if versions in changelog and pubspec do not match', () async { - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, version: '1.0.1'); - const String changelog = ''' -## 1.0.2 -* Some changes. -'''; - plugin.changelogFile.writeAsStringSync(changelog); - Error? commandError; - final List output = await runCapturingPrint( - runner, ['version-check', '--base-sha=main', '--against-pub'], - errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Versions in CHANGELOG.md and pubspec.yaml do not match.'), - ]), - ); - }); - - test('Success if CHANGELOG and pubspec versions match', () async { - const String version = '1.0.1'; - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, version: version); - - const String changelog = ''' -## $version -* Some changes. -'''; - plugin.changelogFile.writeAsStringSync(changelog); - final List output = await runCapturingPrint( - runner, ['version-check', '--base-sha=main']); - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - ]), - ); - }); - - test( - 'Fail if pubspec version only matches an older version listed in CHANGELOG', - () async { - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, version: '1.0.0'); - - const String changelog = ''' -## 1.0.1 -* Some changes. -## 1.0.0 -* Some other changes. -'''; - plugin.changelogFile.writeAsStringSync(changelog); - bool hasError = false; - final List output = await runCapturingPrint( - runner, ['version-check', '--base-sha=main', '--against-pub'], - errorHandler: (Error e) { - expect(e, isA()); - hasError = true; - }); - expect(hasError, isTrue); - - expect( - output, - containsAllInOrder([ - contains('Versions in CHANGELOG.md and pubspec.yaml do not match.'), - ]), - ); - }); - - test('Allow NEXT as a placeholder for gathering CHANGELOG entries', - () async { - const String version = '1.0.0'; - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, version: version); - - const String changelog = ''' -## NEXT -* Some changes that won't be published until the next time there's a release. -## $version -* Some other changes. -'''; - plugin.changelogFile.writeAsStringSync(changelog); - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: 'version: 1.0.0'), - ]; - - final List output = await runCapturingPrint( - runner, ['version-check', '--base-sha=main']); - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('Found NEXT; validating next version in the CHANGELOG.'), - ]), - ); - }); - - test('Fail if NEXT appears after a version', () async { - const String version = '1.0.1'; - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, version: version); - - const String changelog = ''' -## $version -* Some changes. -## NEXT -* Some changes that should have been folded in 1.0.1. -## 1.0.0 -* Some other changes. -'''; - plugin.changelogFile.writeAsStringSync(changelog); - bool hasError = false; - final List output = await runCapturingPrint( - runner, ['version-check', '--base-sha=main', '--against-pub'], - errorHandler: (Error e) { - expect(e, isA()); - hasError = true; - }); - expect(hasError, isTrue); - - expect( - output, - containsAllInOrder([ - contains('When bumping the version for release, the NEXT section ' - "should be incorporated into the new version's release notes.") - ]), - ); - }); - - test('Fail if NEXT is left in the CHANGELOG when adding a version bump', - () async { - const String version = '1.0.1'; - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, version: version); - - const String changelog = ''' -## NEXT -* Some changes that should have been folded in 1.0.1. -## $version -* Some changes. -## 1.0.0 -* Some other changes. -'''; - plugin.changelogFile.writeAsStringSync(changelog); - - bool hasError = false; - final List output = await runCapturingPrint( - runner, ['version-check', '--base-sha=main', '--against-pub'], - errorHandler: (Error e) { - expect(e, isA()); - hasError = true; - }); - expect(hasError, isTrue); - - expect( - output, - containsAllInOrder([ - contains('When bumping the version for release, the NEXT section ' - "should be incorporated into the new version's release notes."), - contains('plugin:\n' - ' CHANGELOG.md failed validation.'), - ]), - ); - }); - - test('fails if the version increases without replacing NEXT', () async { - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, version: '1.0.1'); - - const String changelog = ''' -## NEXT -* Some changes that should be listed as part of 1.0.1. -## 1.0.0 -* Some other changes. -'''; - plugin.changelogFile.writeAsStringSync(changelog); - - bool hasError = false; - final List output = await runCapturingPrint( - runner, ['version-check', '--base-sha=main', '--against-pub'], - errorHandler: (Error e) { - expect(e, isA()); - hasError = true; - }); - expect(hasError, isTrue); - - expect( - output, - containsAllInOrder([ - contains('When bumping the version for release, the NEXT section ' - "should be incorporated into the new version's release notes.") - ]), - ); - }); - - test('allows NEXT for a revert', () async { - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, version: '1.0.0'); - - const String changelog = ''' -## NEXT -* Some changes that should be listed as part of 1.0.1. -## 1.0.0 -* Some other changes. -'''; - plugin.changelogFile.writeAsStringSync(changelog); - plugin.changelogFile.writeAsStringSync(changelog); - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: 'version: 1.0.1'), - ]; - - final List output = await runCapturingPrint( - runner, ['version-check', '--base-sha=main']); - expect( - output, - containsAllInOrder([ - contains('New version is lower than previous version. ' - 'This is assumed to be a revert.'), - ]), - ); - }); - - test( - 'fails gracefully if the version headers are not found due to using the wrong style', - () async { - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, version: '1.0.0'); - - const String changelog = ''' -## NEXT -* Some changes for a later release. -# 1.0.0 -* Some other changes. -'''; - plugin.changelogFile.writeAsStringSync(changelog); - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: 'version: 1.0.0'), - ]; - - Error? commandError; - final List output = await runCapturingPrint(runner, [ - 'version-check', - '--base-sha=main', - ], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Unable to find a version in CHANGELOG.md'), - contains('The current version should be on a line starting with ' - '"## ", either on the first non-empty line or after a "## NEXT" ' - 'section.'), - ]), - ); - }); - - test('fails gracefully if the version is unparseable', () async { - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, version: '1.0.0'); - - const String changelog = ''' -## Alpha -* Some changes. -'''; - plugin.changelogFile.writeAsStringSync(changelog); - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: 'version: 1.0.0'), - ]; - - Error? commandError; - final List output = await runCapturingPrint(runner, [ - 'version-check', - '--base-sha=main', - ], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('"Alpha" could not be parsed as a version.'), - ]), - ); - }); - - group('missing change detection', () { - Future> runWithMissingChangeDetection(List extraArgs, - {void Function(Error error)? errorHandler}) async { - return runCapturingPrint( - runner, - [ - 'version-check', - '--base-sha=main', - '--check-for-missing-changes', - ...extraArgs, - ], - errorHandler: errorHandler); - } - - test('passes for unchanged packages', () async { - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, version: '1.0.0'); - - const String changelog = ''' -## 1.0.0 -* Some changes. -'''; - plugin.changelogFile.writeAsStringSync(changelog); - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: 'version: 1.0.0'), - ]; - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: ''), - ]; - - final List output = - await runWithMissingChangeDetection([]); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - ]), - ); - }); - - test( - 'fails if a version change is missing from a change that does not ' - 'pass the exemption check', () async { - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, version: '1.0.0'); - - const String changelog = ''' -## 1.0.0 -* Some changes. -'''; - plugin.changelogFile.writeAsStringSync(changelog); - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: 'version: 1.0.0'), - ]; - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: ''' -packages/plugin/lib/plugin.dart -'''), - ]; - - Error? commandError; - final List output = await runWithMissingChangeDetection( - [], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('No version change found'), - contains('plugin:\n' - ' Missing version change'), - ]), - ); - }); - - test('passes version change requirement when version changes', () async { - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, version: '1.0.1'); - - const String changelog = ''' -## 1.0.1 -* Some changes. -'''; - plugin.changelogFile.writeAsStringSync(changelog); - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: 'version: 1.0.0'), - ]; - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: ''' -packages/plugin/lib/plugin.dart -packages/plugin/CHANGELOG.md -packages/plugin/pubspec.yaml -'''), - ]; - - final List output = - await runWithMissingChangeDetection([]); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - ]), - ); - }); - - test('version change check ignores files outside the package', () async { - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, version: '1.0.0'); - - const String changelog = ''' -## 1.0.0 -* Some changes. -'''; - plugin.changelogFile.writeAsStringSync(changelog); - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: 'version: 1.0.0'), - ]; - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: ''' -packages/plugin_a/lib/plugin.dart -tool/plugin/lib/plugin.dart -'''), - ]; - - final List output = - await runWithMissingChangeDetection([]); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - ]), - ); - }); - - test('allows missing version change for exempt changes', () async { - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, version: '1.0.0'); - - const String changelog = ''' -## 1.0.0 -* Some changes. -'''; - plugin.changelogFile.writeAsStringSync(changelog); - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: 'version: 1.0.0'), - ]; - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: ''' -packages/plugin/example/android/lint-baseline.xml -packages/plugin/example/android/src/androidTest/foo/bar/FooTest.java -packages/plugin/example/ios/RunnerTests/Foo.m -packages/plugin/example/ios/RunnerUITests/info.plist -packages/plugin/CHANGELOG.md -'''), - ]; - - final List output = - await runWithMissingChangeDetection([]); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - ]), - ); - }); - - test('allows missing version change with override label', () async { - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, version: '1.0.0'); - - const String changelog = ''' -## 1.0.0 -* Some changes. -'''; - plugin.changelogFile.writeAsStringSync(changelog); - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: 'version: 1.0.0'), - ]; - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: ''' -packages/plugin/lib/plugin.dart -packages/plugin/CHANGELOG.md -packages/plugin/pubspec.yaml -'''), - ]; - - final List output = - await runWithMissingChangeDetection([ - '--pr-labels=some label,override: no versioning needed,another-label' - ]); - - expect( - output, - containsAllInOrder([ - contains('Ignoring lack of version change due to the ' - '"override: no versioning needed" label.'), - ]), - ); - }); - - test('fails if a CHANGELOG change is missing', () async { - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, version: '1.0.0'); - - const String changelog = ''' -## 1.0.0 -* Some changes. -'''; - plugin.changelogFile.writeAsStringSync(changelog); - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: 'version: 1.0.0'), - ]; - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: ''' -packages/plugin/example/lib/foo.dart -'''), - ]; - - Error? commandError; - final List output = await runWithMissingChangeDetection( - [], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('No CHANGELOG change found'), - contains('plugin:\n' - ' Missing CHANGELOG change'), - ]), - ); - }); - - test('passes CHANGELOG check when the CHANGELOG is changed', () async { - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, version: '1.0.0'); - - const String changelog = ''' -## 1.0.0 -* Some changes. -'''; - plugin.changelogFile.writeAsStringSync(changelog); - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: 'version: 1.0.0'), - ]; - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: ''' -packages/plugin/example/lib/foo.dart -packages/plugin/CHANGELOG.md -'''), - ]; - - final List output = - await runWithMissingChangeDetection([]); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - ]), - ); - }); - - test('fails CHANGELOG check if only another package CHANGELOG chages', - () async { - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, version: '1.0.0'); - - const String changelog = ''' -## 1.0.0 -* Some changes. -'''; - plugin.changelogFile.writeAsStringSync(changelog); - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: 'version: 1.0.0'), - ]; - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: ''' -packages/plugin/example/lib/foo.dart -packages/another_plugin/CHANGELOG.md -'''), - ]; - - Error? commandError; - final List output = await runWithMissingChangeDetection( - [], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('No CHANGELOG change found'), - ]), - ); - }); - - test('allows missing CHANGELOG change with justification', () async { - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, version: '1.0.0'); - - const String changelog = ''' -## 1.0.0 -* Some changes. -'''; - plugin.changelogFile.writeAsStringSync(changelog); - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: 'version: 1.0.0'), - ]; - processRunner.mockProcessesForExecutable['git-diff'] = [ - MockProcess(stdout: ''' -packages/plugin/example/lib/foo.dart -'''), - ]; - - final List output = - await runWithMissingChangeDetection([ - '--pr-labels=some label,override: no changelog needed,another-label' - ]); - - expect( - output, - containsAllInOrder([ - contains('Ignoring lack of CHANGELOG update due to the ' - '"override: no changelog needed" label.'), - ]), - ); - }); - - // This test ensures that Dependabot Gradle changes to test-only files - // aren't flagged by the version check. - test( - 'allows missing CHANGELOG and version change for test-only Gradle changes', - () async { - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, version: '1.0.0'); - - const String changelog = ''' -## 1.0.0 -* Some changes. -'''; - plugin.changelogFile.writeAsStringSync(changelog); - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: 'version: 1.0.0'), - ]; - processRunner.mockProcessesForExecutable['git-diff'] = [ - // File list. - MockProcess(stdout: ''' -packages/plugin/android/build.gradle -'''), - // build.gradle diff - MockProcess(stdout: ''' -- androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' -- testImplementation 'junit:junit:4.10.0' -+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' -+ testImplementation 'junit:junit:4.13.2' -'''), - ]; - - final List output = - await runWithMissingChangeDetection([]); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - ]), - ); - }); - - test('allows missing CHANGELOG and version change for dev-only changes', - () async { - final RepositoryPackage plugin = - createFakePlugin('plugin', packagesDir, version: '1.0.0'); - - const String changelog = ''' -## 1.0.0 -* Some changes. -'''; - plugin.changelogFile.writeAsStringSync(changelog); - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: 'version: 1.0.0'), - ]; - processRunner.mockProcessesForExecutable['git-diff'] = [ - // File list. - MockProcess(stdout: ''' -packages/plugin/tool/run_tests.dart -packages/plugin/run_tests.sh -'''), - ]; - - final List output = - await runWithMissingChangeDetection([]); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - ]), - ); - }); - }); - - test('allows valid against pub', () async { - mockHttpResponse = { - 'name': 'some_package', - 'versions': [ - '0.0.1', - '0.0.2', - '1.0.0', - ], - }; - - createFakePlugin('plugin', packagesDir, version: '2.0.0'); - final List output = await runCapturingPrint(runner, - ['version-check', '--base-sha=main', '--against-pub']); - - expect( - output, - containsAllInOrder([ - contains('plugin: Current largest version on pub: 1.0.0'), - ]), - ); - }); - - test('denies invalid against pub', () async { - mockHttpResponse = { - 'name': 'some_package', - 'versions': [ - '0.0.1', - '0.0.2', - ], - }; - - createFakePlugin('plugin', packagesDir, version: '2.0.0'); - - bool hasError = false; - final List result = await runCapturingPrint( - runner, ['version-check', '--base-sha=main', '--against-pub'], - errorHandler: (Error e) { - expect(e, isA()); - hasError = true; - }); - expect(hasError, isTrue); - - expect( - result, - containsAllInOrder([ - contains(''' -${indentation}Incorrectly updated version. -${indentation}HEAD: 2.0.0, pub: 0.0.2. -${indentation}Allowed versions: {1.0.0: NextVersionType.BREAKING_MAJOR, 0.1.0: NextVersionType.MINOR, 0.0.3: NextVersionType.PATCH}''') - ]), - ); - }); - - test( - 'throw and print error message if http request failed when checking against pub', - () async { - mockHttpStatus = 400; - - createFakePlugin('plugin', packagesDir, version: '2.0.0'); - bool hasError = false; - final List result = await runCapturingPrint( - runner, ['version-check', '--base-sha=main', '--against-pub'], - errorHandler: (Error e) { - expect(e, isA()); - hasError = true; - }); - expect(hasError, isTrue); - - expect( - result, - containsAllInOrder([ - contains(''' -${indentation}Error fetching version on pub for plugin. -${indentation}HTTP Status 400 -${indentation}HTTP response: null -''') - ]), - ); - }); - - test('when checking against pub, allow any version if http status is 404.', - () async { - mockHttpStatus = 404; - - createFakePlugin('plugin', packagesDir, version: '2.0.0'); - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: 'version: 1.0.0'), - ]; - final List result = await runCapturingPrint(runner, - ['version-check', '--base-sha=main', '--against-pub']); - - expect( - result, - containsAllInOrder([ - contains('Unable to find previous version on pub server.'), - ]), - ); - }); - - group('prelease versions', () { - test( - 'allow an otherwise-valid transition that also adds a pre-release component', - () async { - createFakePlugin('plugin', packagesDir, version: '2.0.0-dev'); - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: 'version: 1.0.0'), - ]; - final List output = await runCapturingPrint( - runner, ['version-check', '--base-sha=main']); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('1.0.0 -> 2.0.0-dev'), - ]), - ); - expect( - processRunner.recordedCalls, - containsAllInOrder(const [ - ProcessCall('git-show', - ['main:packages/plugin/pubspec.yaml'], null) - ])); - }); - - test('allow releasing a pre-release', () async { - createFakePlugin('plugin', packagesDir, version: '1.2.0'); - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: 'version: 1.2.0-dev'), - ]; - final List output = await runCapturingPrint( - runner, ['version-check', '--base-sha=main']); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('1.2.0-dev -> 1.2.0'), - ]), - ); - expect( - processRunner.recordedCalls, - containsAllInOrder(const [ - ProcessCall('git-show', - ['main:packages/plugin/pubspec.yaml'], null) - ])); - }); - - // Allow abandoning a pre-release version in favor of a different version - // change type. - test( - 'allow an otherwise-valid transition that also removes a pre-release component', - () async { - createFakePlugin('plugin', packagesDir, version: '2.0.0'); - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: 'version: 1.2.0-dev'), - ]; - final List output = await runCapturingPrint( - runner, ['version-check', '--base-sha=main']); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('1.2.0-dev -> 2.0.0'), - ]), - ); - expect( - processRunner.recordedCalls, - containsAllInOrder(const [ - ProcessCall('git-show', - ['main:packages/plugin/pubspec.yaml'], null) - ])); - }); - - test('allow changing only the pre-release version', () async { - createFakePlugin('plugin', packagesDir, version: '1.2.0-dev.2'); - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: 'version: 1.2.0-dev.1'), - ]; - final List output = await runCapturingPrint( - runner, ['version-check', '--base-sha=main']); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('1.2.0-dev.1 -> 1.2.0-dev.2'), - ]), - ); - expect( - processRunner.recordedCalls, - containsAllInOrder(const [ - ProcessCall('git-show', - ['main:packages/plugin/pubspec.yaml'], null) - ])); - }); - - test('denies invalid version change that also adds a pre-release', - () async { - createFakePlugin('plugin', packagesDir, version: '0.2.0-dev'); - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: 'version: 0.0.1'), - ]; - Error? commandError; - final List output = await runCapturingPrint( - runner, ['version-check', '--base-sha=main'], - errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Incorrectly updated version.'), - ])); - expect( - processRunner.recordedCalls, - containsAllInOrder(const [ - ProcessCall('git-show', - ['main:packages/plugin/pubspec.yaml'], null) - ])); - }); - - test('denies invalid version change that also removes a pre-release', - () async { - createFakePlugin('plugin', packagesDir, version: '0.2.0'); - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: 'version: 0.0.1-dev'), - ]; - Error? commandError; - final List output = await runCapturingPrint( - runner, ['version-check', '--base-sha=main'], - errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Incorrectly updated version.'), - ])); - expect( - processRunner.recordedCalls, - containsAllInOrder(const [ - ProcessCall('git-show', - ['main:packages/plugin/pubspec.yaml'], null) - ])); - }); - - test('denies invalid version change between pre-releases', () async { - createFakePlugin('plugin', packagesDir, version: '0.2.0-dev'); - processRunner.mockProcessesForExecutable['git-show'] = [ - MockProcess(stdout: 'version: 0.0.1-dev'), - ]; - Error? commandError; - final List output = await runCapturingPrint( - runner, ['version-check', '--base-sha=main'], - errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('Incorrectly updated version.'), - ])); - expect( - processRunner.recordedCalls, - containsAllInOrder(const [ - ProcessCall('git-show', - ['main:packages/plugin/pubspec.yaml'], null) - ])); - }); - }); - }); - - group('Pre 1.0', () { - test('nextVersion allows patch version', () { - testAllowedVersion('0.12.0', '0.12.0+1', - nextVersionType: NextVersionType.PATCH); - testAllowedVersion('0.12.0+4', '0.12.0+5', - nextVersionType: NextVersionType.PATCH); - }); - - test('nextVersion does not allow jumping patch', () { - testAllowedVersion('0.12.0', '0.12.0+2', allowed: false); - testAllowedVersion('0.12.0+2', '0.12.0+4', allowed: false); - }); - - test('nextVersion does not allow going back', () { - testAllowedVersion('0.12.0', '0.11.0', allowed: false); - testAllowedVersion('0.12.0+2', '0.12.0+1', allowed: false); - testAllowedVersion('0.12.0+1', '0.12.0', allowed: false); - }); - - test('nextVersion allows minor version', () { - testAllowedVersion('0.12.0', '0.12.1', - nextVersionType: NextVersionType.MINOR); - testAllowedVersion('0.12.0+4', '0.12.1', - nextVersionType: NextVersionType.MINOR); - }); - - test('nextVersion does not allow jumping minor', () { - testAllowedVersion('0.12.0', '0.12.2', allowed: false); - testAllowedVersion('0.12.0+2', '0.12.3', allowed: false); - }); - }); - - group('Releasing 1.0', () { - test('nextVersion allows releasing 1.0', () { - testAllowedVersion('0.12.0', '1.0.0', - nextVersionType: NextVersionType.BREAKING_MAJOR); - testAllowedVersion('0.12.0+4', '1.0.0', - nextVersionType: NextVersionType.BREAKING_MAJOR); - }); - - test('nextVersion does not allow jumping major', () { - testAllowedVersion('0.12.0', '2.0.0', allowed: false); - testAllowedVersion('0.12.0+4', '2.0.0', allowed: false); - }); - - test('nextVersion does not allow un-releasing', () { - testAllowedVersion('1.0.0', '0.12.0+4', allowed: false); - testAllowedVersion('1.0.0', '0.12.0', allowed: false); - }); - }); - - group('Post 1.0', () { - test('nextVersion allows patch jumps', () { - testAllowedVersion('1.0.1', '1.0.2', - nextVersionType: NextVersionType.PATCH); - testAllowedVersion('1.0.0', '1.0.1', - nextVersionType: NextVersionType.PATCH); - }); - - test('nextVersion does not allow build jumps', () { - testAllowedVersion('1.0.1', '1.0.1+1', allowed: false); - testAllowedVersion('1.0.0+5', '1.0.0+6', allowed: false); - }); - - test('nextVersion does not allow skipping patches', () { - testAllowedVersion('1.0.1', '1.0.3', allowed: false); - testAllowedVersion('1.0.0', '1.0.6', allowed: false); - }); - - test('nextVersion allows minor version jumps', () { - testAllowedVersion('1.0.1', '1.1.0', - nextVersionType: NextVersionType.MINOR); - testAllowedVersion('1.0.0', '1.1.0', - nextVersionType: NextVersionType.MINOR); - }); - - test('nextVersion does not allow skipping minor versions', () { - testAllowedVersion('1.0.1', '1.2.0', allowed: false); - testAllowedVersion('1.1.0', '1.3.0', allowed: false); - }); - - test('nextVersion allows breaking changes', () { - testAllowedVersion('1.0.1', '2.0.0', - nextVersionType: NextVersionType.BREAKING_MAJOR); - testAllowedVersion('1.0.0', '2.0.0', - nextVersionType: NextVersionType.BREAKING_MAJOR); - }); - - test('nextVersion does not allow skipping major versions', () { - testAllowedVersion('1.0.1', '3.0.0', allowed: false); - testAllowedVersion('1.1.0', '2.3.0', allowed: false); - }); - }); -} diff --git a/script/tool/test/xcode_analyze_command_test.dart b/script/tool/test/xcode_analyze_command_test.dart deleted file mode 100644 index 418c695f295c..000000000000 --- a/script/tool/test/xcode_analyze_command_test.dart +++ /dev/null @@ -1,484 +0,0 @@ -// Copyright 2013 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. - -import 'dart:io' as io; - -import 'package:args/command_runner.dart'; -import 'package:file/file.dart'; -import 'package:file/memory.dart'; -import 'package:flutter_plugin_tools/src/common/core.dart'; -import 'package:flutter_plugin_tools/src/common/plugin_utils.dart'; -import 'package:flutter_plugin_tools/src/xcode_analyze_command.dart'; -import 'package:test/test.dart'; - -import 'mocks.dart'; -import 'util.dart'; - -// TODO(stuartmorgan): Rework these tests to use a mock Xcode instead of -// doing all the process mocking and validation. -void main() { - group('test xcode_analyze_command', () { - late FileSystem fileSystem; - late MockPlatform mockPlatform; - late Directory packagesDir; - late CommandRunner runner; - late RecordingProcessRunner processRunner; - - setUp(() { - fileSystem = MemoryFileSystem(); - mockPlatform = MockPlatform(isMacOS: true); - packagesDir = createPackagesDirectory(fileSystem: fileSystem); - processRunner = RecordingProcessRunner(); - final XcodeAnalyzeCommand command = XcodeAnalyzeCommand(packagesDir, - processRunner: processRunner, platform: mockPlatform); - - runner = CommandRunner( - 'xcode_analyze_command', 'Test for xcode_analyze_command'); - runner.addCommand(command); - }); - - test('Fails if no platforms are provided', () async { - Error? commandError; - final List output = await runCapturingPrint( - runner, ['xcode-analyze'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('At least one platform flag must be provided'), - ]), - ); - }); - - group('iOS', () { - test('skip if iOS is not supported', () async { - createFakePlugin('plugin', packagesDir, - platformSupport: { - platformMacOS: const PlatformDetails(PlatformSupport.inline), - }); - - final List output = - await runCapturingPrint(runner, ['xcode-analyze', '--ios']); - expect(output, - contains(contains('Not implemented for target platform(s).'))); - expect(processRunner.recordedCalls, orderedEquals([])); - }); - - test('skip if iOS is implemented in a federated package', () async { - createFakePlugin('plugin', packagesDir, - platformSupport: { - platformIOS: const PlatformDetails(PlatformSupport.federated) - }); - - final List output = - await runCapturingPrint(runner, ['xcode-analyze', '--ios']); - expect(output, - contains(contains('Not implemented for target platform(s).'))); - expect(processRunner.recordedCalls, orderedEquals([])); - }); - - test('runs for iOS plugin', () async { - final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir, - platformSupport: { - platformIOS: const PlatformDetails(PlatformSupport.inline) - }); - - final Directory pluginExampleDirectory = getExampleDir(plugin); - - final List output = await runCapturingPrint(runner, [ - 'xcode-analyze', - '--ios', - ]); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('plugin/example (iOS) passed analysis.') - ])); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - 'xcrun', - const [ - 'xcodebuild', - 'analyze', - '-workspace', - 'ios/Runner.xcworkspace', - '-scheme', - 'Runner', - '-configuration', - 'Debug', - '-destination', - 'generic/platform=iOS Simulator', - 'GCC_TREAT_WARNINGS_AS_ERRORS=YES', - ], - pluginExampleDirectory.path), - ])); - }); - - test('passes min iOS deployment version when requested', () async { - final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir, - platformSupport: { - platformIOS: const PlatformDetails(PlatformSupport.inline) - }); - - final Directory pluginExampleDirectory = getExampleDir(plugin); - - final List output = await runCapturingPrint(runner, - ['xcode-analyze', '--ios', '--ios-min-version=14.0']); - - expect( - output, - containsAllInOrder([ - contains('Running for plugin'), - contains('plugin/example (iOS) passed analysis.') - ])); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - 'xcrun', - const [ - 'xcodebuild', - 'analyze', - '-workspace', - 'ios/Runner.xcworkspace', - '-scheme', - 'Runner', - '-configuration', - 'Debug', - '-destination', - 'generic/platform=iOS Simulator', - 'IPHONEOS_DEPLOYMENT_TARGET=14.0', - 'GCC_TREAT_WARNINGS_AS_ERRORS=YES', - ], - pluginExampleDirectory.path), - ])); - }); - - test('fails if xcrun fails', () async { - createFakePlugin('plugin', packagesDir, - platformSupport: { - platformIOS: const PlatformDetails(PlatformSupport.inline) - }); - - processRunner.mockProcessesForExecutable['xcrun'] = [ - MockProcess(exitCode: 1) - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, - [ - 'xcode-analyze', - '--ios', - ], - errorHandler: (Error e) { - commandError = e; - }, - ); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('The following packages had errors:'), - contains(' plugin'), - ])); - }); - }); - - group('macOS', () { - test('skip if macOS is not supported', () async { - createFakePlugin( - 'plugin', - packagesDir, - ); - - final List output = await runCapturingPrint( - runner, ['xcode-analyze', '--macos']); - expect(output, - contains(contains('Not implemented for target platform(s).'))); - expect(processRunner.recordedCalls, orderedEquals([])); - }); - - test('skip if macOS is implemented in a federated package', () async { - createFakePlugin('plugin', packagesDir, - platformSupport: { - platformMacOS: const PlatformDetails(PlatformSupport.federated), - }); - - final List output = await runCapturingPrint( - runner, ['xcode-analyze', '--macos']); - expect(output, - contains(contains('Not implemented for target platform(s).'))); - expect(processRunner.recordedCalls, orderedEquals([])); - }); - - test('runs for macOS plugin', () async { - final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir, - platformSupport: { - platformMacOS: const PlatformDetails(PlatformSupport.inline), - }); - - final Directory pluginExampleDirectory = getExampleDir(plugin); - - final List output = await runCapturingPrint(runner, [ - 'xcode-analyze', - '--macos', - ]); - - expect(output, - contains(contains('plugin/example (macOS) passed analysis.'))); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - 'xcrun', - const [ - 'xcodebuild', - 'analyze', - '-workspace', - 'macos/Runner.xcworkspace', - '-scheme', - 'Runner', - '-configuration', - 'Debug', - 'GCC_TREAT_WARNINGS_AS_ERRORS=YES', - ], - pluginExampleDirectory.path), - ])); - }); - - test('passes min macOS deployment version when requested', () async { - final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir, - platformSupport: { - platformMacOS: const PlatformDetails(PlatformSupport.inline), - }); - - final Directory pluginExampleDirectory = getExampleDir(plugin); - - final List output = await runCapturingPrint(runner, - ['xcode-analyze', '--macos', '--macos-min-version=12.0']); - - expect(output, - contains(contains('plugin/example (macOS) passed analysis.'))); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - 'xcrun', - const [ - 'xcodebuild', - 'analyze', - '-workspace', - 'macos/Runner.xcworkspace', - '-scheme', - 'Runner', - '-configuration', - 'Debug', - 'MACOSX_DEPLOYMENT_TARGET=12.0', - 'GCC_TREAT_WARNINGS_AS_ERRORS=YES', - ], - pluginExampleDirectory.path), - ])); - }); - - test('fails if xcrun fails', () async { - createFakePlugin('plugin', packagesDir, - platformSupport: { - platformMacOS: const PlatformDetails(PlatformSupport.inline), - }); - - processRunner.mockProcessesForExecutable['xcrun'] = [ - MockProcess(exitCode: 1) - ]; - - Error? commandError; - final List output = await runCapturingPrint( - runner, ['xcode-analyze', '--macos'], - errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA()); - expect( - output, - containsAllInOrder([ - contains('The following packages had errors:'), - contains(' plugin'), - ]), - ); - }); - }); - - group('combined', () { - test('runs both iOS and macOS when supported', () async { - final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir, - platformSupport: { - platformIOS: const PlatformDetails(PlatformSupport.inline), - platformMacOS: const PlatformDetails(PlatformSupport.inline), - }); - - final Directory pluginExampleDirectory = getExampleDir(plugin); - - final List output = await runCapturingPrint(runner, [ - 'xcode-analyze', - '--ios', - '--macos', - ]); - - expect( - output, - containsAll([ - contains('plugin/example (iOS) passed analysis.'), - contains('plugin/example (macOS) passed analysis.'), - ])); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - 'xcrun', - const [ - 'xcodebuild', - 'analyze', - '-workspace', - 'ios/Runner.xcworkspace', - '-scheme', - 'Runner', - '-configuration', - 'Debug', - '-destination', - 'generic/platform=iOS Simulator', - 'GCC_TREAT_WARNINGS_AS_ERRORS=YES', - ], - pluginExampleDirectory.path), - ProcessCall( - 'xcrun', - const [ - 'xcodebuild', - 'analyze', - '-workspace', - 'macos/Runner.xcworkspace', - '-scheme', - 'Runner', - '-configuration', - 'Debug', - 'GCC_TREAT_WARNINGS_AS_ERRORS=YES', - ], - pluginExampleDirectory.path), - ])); - }); - - test('runs only macOS for a macOS plugin', () async { - final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir, - platformSupport: { - platformMacOS: const PlatformDetails(PlatformSupport.inline), - }); - - final Directory pluginExampleDirectory = getExampleDir(plugin); - - final List output = await runCapturingPrint(runner, [ - 'xcode-analyze', - '--ios', - '--macos', - ]); - - expect( - output, - containsAllInOrder([ - contains('plugin/example (macOS) passed analysis.'), - ])); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - 'xcrun', - const [ - 'xcodebuild', - 'analyze', - '-workspace', - 'macos/Runner.xcworkspace', - '-scheme', - 'Runner', - '-configuration', - 'Debug', - 'GCC_TREAT_WARNINGS_AS_ERRORS=YES', - ], - pluginExampleDirectory.path), - ])); - }); - - test('runs only iOS for a iOS plugin', () async { - final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir, - platformSupport: { - platformIOS: const PlatformDetails(PlatformSupport.inline) - }); - - final Directory pluginExampleDirectory = getExampleDir(plugin); - - final List output = await runCapturingPrint(runner, [ - 'xcode-analyze', - '--ios', - '--macos', - ]); - - expect( - output, - containsAllInOrder( - [contains('plugin/example (iOS) passed analysis.')])); - - expect( - processRunner.recordedCalls, - orderedEquals([ - ProcessCall( - 'xcrun', - const [ - 'xcodebuild', - 'analyze', - '-workspace', - 'ios/Runner.xcworkspace', - '-scheme', - 'Runner', - '-configuration', - 'Debug', - '-destination', - 'generic/platform=iOS Simulator', - 'GCC_TREAT_WARNINGS_AS_ERRORS=YES', - ], - pluginExampleDirectory.path), - ])); - }); - - test('skips when neither are supported', () async { - createFakePlugin('plugin', packagesDir); - - final List output = await runCapturingPrint(runner, [ - 'xcode-analyze', - '--ios', - '--macos', - ]); - - expect( - output, - containsAllInOrder([ - contains('SKIPPING: Not implemented for target platform(s).'), - ])); - - expect(processRunner.recordedCalls, orderedEquals([])); - }); - }); - }); -} diff --git a/script/tool_runner.sh b/script/tool_runner.sh index 221071550cc1..ba7bec6579d1 100755 --- a/script/tool_runner.sh +++ b/script/tool_runner.sh @@ -6,18 +6,20 @@ set -e # WARNING! Do not remove this script, or change its behavior, unless you have -# verified that it will not break the flutter/flutter analysis run of this -# repository: https://github.com/flutter/flutter/blob/master/dev/bots/test.dart +# verified that it will not break the dart-lang analysis run of this +# repository: https://github.com/dart-lang/sdk/blob/main/tools/bots/flutter/analyze_flutter_plugins.sh readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" readonly REPO_DIR="$(dirname "$SCRIPT_DIR")" -readonly TOOL_PATH="$REPO_DIR/script/tool/bin/flutter_plugin_tools.dart" -# Ensure that the tool dependencies have been fetched. -(pushd "$REPO_DIR/script/tool" && dart pub get && popd) >/dev/null # The tool expects to be run from the repo root. -cd "$REPO_DIR" -# Run from the in-tree source. # PACKAGE_SHARDING is (optionally) set from Cirrus. See .cirrus.yml -dart run "$TOOL_PATH" "$@" --packages-for-branch --log-timing $PACKAGE_SHARDING +cd "$REPO_DIR" +# Ensure that the tooling has been activated. +.ci/scripts/prepare_tool.sh + +dart pub global run flutter_plugin_tools "$@" \ + --packages-for-branch \ + --log-timing \ + $PACKAGE_SHARDING From 2ce625f1a87e4b738f63a6f1f7e3e040e7c0b87f Mon Sep 17 00:00:00 2001 From: Camille Simon <43054281+camsim99@users.noreply.github.com> Date: Tue, 14 Feb 2023 14:54:12 -0800 Subject: [PATCH 103/130] [cameraX] Add integration test for availableCameras (#7156) * Add integration test * Add integration test --- .../camera/camera_android_camerax/CHANGELOG.md | 1 + .../integration_test/integration_test.dart | 18 +++++++++++++++++- .../camera/camera_android_camerax/pubspec.yaml | 2 ++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/packages/camera/camera_android_camerax/CHANGELOG.md b/packages/camera/camera_android_camerax/CHANGELOG.md index 50fdf586c5e7..06f41a68a146 100644 --- a/packages/camera/camera_android_camerax/CHANGELOG.md +++ b/packages/camera/camera_android_camerax/CHANGELOG.md @@ -10,3 +10,4 @@ * Changes instance manager to allow the separate creation of identical objects. * Adds Preview and Surface classes, along with other methods needed to implement camera preview. * Adds implementation of availableCameras() +* Adds integration test to plugin. diff --git a/packages/camera/camera_android_camerax/example/integration_test/integration_test.dart b/packages/camera/camera_android_camerax/example/integration_test/integration_test.dart index 2b82b4bda5e4..b05d14a9cc79 100644 --- a/packages/camera/camera_android_camerax/example/integration_test/integration_test.dart +++ b/packages/camera/camera_android_camerax/example/integration_test/integration_test.dart @@ -2,11 +2,27 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:camera_android_camerax/camera_android_camerax.dart'; +import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); - testWidgets('placeholder test', (WidgetTester tester) async {}); + setUpAll(() async { + CameraPlatform.instance = AndroidCameraCameraX(); + }); + + testWidgets('availableCameras only supports valid back or front cameras', + (WidgetTester tester) async { + final List availableCameras = + await CameraPlatform.instance.availableCameras(); + + for (final CameraDescription cameraDescription in availableCameras) { + expect( + cameraDescription.lensDirection, isNot(CameraLensDirection.external)); + expect(cameraDescription.sensorOrientation, anyOf(0, 90, 180, 270)); + } + }); } diff --git a/packages/camera/camera_android_camerax/pubspec.yaml b/packages/camera/camera_android_camerax/pubspec.yaml index 7f81ecbd4f71..45d5a2c66abd 100644 --- a/packages/camera/camera_android_camerax/pubspec.yaml +++ b/packages/camera/camera_android_camerax/pubspec.yaml @@ -21,6 +21,8 @@ dependencies: camera_platform_interface: ^2.2.0 flutter: sdk: flutter + integration_test: + sdk: flutter stream_transform: ^2.1.0 dev_dependencies: From d699b4a91381c213d67345938d73d6395bd64a06 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Wed, 15 Feb 2023 10:55:39 -0500 Subject: [PATCH 104/130] Roll Flutter from e3471f08d1d3 to df41e58f6f4e (83 revisions) (#7184) * 001c4951b Roll Flutter Engine from 9a40a384997d to e1d0032029e4 (6 revisions) (flutter/flutter#120414) * 96823590e post submit only (flutter/flutter#120411) * 5dbd28101 Use String.codeUnitAt instead of String.codeUnits[] in ParagraphBoundary (flutter/flutter#120234) * f05a555bc Fix lerping for `NavigationRailThemeData` icon themes (flutter/flutter#120066) * b0d04ea49 add9e11ed Fix position of BackdropFilter above PlatformView (flutter/engine#39244) (flutter/flutter#120415) * 858f94cfa Roll Plugins from 73986f4cc857 to 02571ec0dd36 (3 revisions) (flutter/flutter#120443) * 298c874ea Fix classes that shouldn't be extended/instantiated/mixedin (flutter/flutter#120409) * 25c2c22d2 Delete Chrome temp cache after closing (flutter/flutter#119062) * b2e37c659 [conductor] Tag engine versions (flutter/flutter#120419) * 780c9a8de Remove deprecated SystemChrome.setEnabledSystemUIOverlays (flutter/flutter#119576) * 52ab29936 Roll Flutter Engine from add9e11edb66 to 4104eb5cbc40 (14 revisions) (flutter/flutter#120470) * 65fd924d8 [conductor] Remove CiYaml model (flutter/flutter#120458) * d5dbcb708 Revert "Revert "[web] Move JS content to its own `.js` files (#117691)" (#120275)" (flutter/flutter#120363) * 2f9abd20f Roll Flutter Engine from 4104eb5cbc40 to 6660300ea34f (6 revisions) (flutter/flutter#120487) * d63c54c9c roll packages (flutter/flutter#120493) * 941578ea9 Roll Flutter Engine from 6660300ea34f to 5e3ff1e5c9b3 (4 revisions) (flutter/flutter#120495) * b3613c4cf f737dc868 [macOS] Add XCode marks for TextInputPlugin (flutter/engine#39550) (flutter/flutter#120501) * 4ab2ffd58 Roll Flutter Engine from f737dc868a3e to 3ac3338489d2 (2 revisions) (flutter/flutter#120505) * 859d57b3c 8baee2164 Roll Skia from 5230650dc096 to 70a3a194ec98 (2 revisions) (flutter/engine#39554) (flutter/flutter#120507) * e42ab1cb0 c3dc68e0e Roll Skia from 70a3a194ec98 to 6c3097e6f833 (1 revision) (flutter/engine#39555) (flutter/flutter#120508) * 61e059f44 Roll Flutter Engine from c3dc68e0e263 to 363355af5158 (2 revisions) (flutter/flutter#120511) * d27de1de0 5efb42971 Roll Fuchsia Linux SDK from 482Njb1v72P7fNyj4... to MVMTNxWJaWdwPWstz... (flutter/engine#39559) (flutter/flutter#120512) * bbf8446d9 761891200 Roll Dart SDK from 1d26a1d57edf to b1836aacc08a (1 revision) (flutter/engine#39561) (flutter/flutter#120513) * 0346f4b18 d8e01097e Roll Fuchsia Mac SDK from 6nMZjuYXTcnD_VZQI... to FxFPRn_9rSWWAWFw0... (flutter/engine#39562) (flutter/flutter#120519) * 0db47bdd0 Revert "Fix BottomAppBar & BottomSheet M3 shadow (#119819)" (flutter/flutter#120492) * f04600bfb Roll Flutter Engine from d8e01097e66b to 2cba062f9f4c (2 revisions) (flutter/flutter#120538) * 274f6cb2d Roll Flutter Engine from 2cba062f9f4c to 05d81c0f2ebe (2 revisions) (flutter/flutter#120547) * d6ff0f2af fe56a45b4 Roll Dart SDK from c4255cea566a to 1caf3a9ad101 (1 revision) (flutter/engine#39571) (flutter/flutter#120552) * 7295d4fe9 9a19d7eea Roll Fuchsia Linux SDK from arbaBzyUE2ok1bGl5... to 8fdyKaKQqTPpjcp-L... (flutter/engine#39572) (flutter/flutter#120554) * 6d68eb7b8 0aa4fcbd2 Roll Skia from ec87ec6fd34f to 615965d545f4 (1 revision) (flutter/engine#39573) (flutter/flutter#120558) * 527977b6a b7e80ad6e Roll Fuchsia Mac SDK from y35kWL0rP5Nd06lTg... to KpTOXssqVhPv2OBZi... (flutter/engine#39574) (flutter/flutter#120559) * 238b0dbc0 Roll Flutter Engine from b7e80ad6ef51 to 1ff345ce5f63 (2 revisions) (flutter/flutter#120574) * 3e659cf71 1eef041d4 [Impeller] Source the pipeline color attachment pixel format from RenderPass textures (flutter/engine#39556) (flutter/flutter#120576) * 53fe8a3f9 4107a7b71 Roll Skia from 615965d545f4 to c6f1de2239fb (1 revision) (flutter/engine#39581) (flutter/flutter#120580) * b0c24e8d3 fix a Slider theme update bug (flutter/flutter#120432) * b33c76f01 1695b7bbc Bump github/codeql-action from 2.1.39 to 2.2.4 (flutter/engine#39584) (flutter/flutter#120588) * ce8efb439 ede2a0a3c Roll Skia from c6f1de2239fb to d85501fa487d (1 revision) (flutter/engine#39585) (flutter/flutter#120593) * 7fb8497b5 Roll Plugins from 02571ec0dd36 to f3bc6f1eb0c2 (2 revisions) (flutter/flutter#120601) * 7c2d5b9c2 60c8532d6 Roll Fuchsia Mac SDK from NZAnfCkpbswhYplty... to 6hbPQq6ED0PkuQiKM... (flutter/engine#39587) (flutter/flutter#120602) * 1f268f1d6 fb8840578 Roll Dart SDK from 1caf3a9ad101 to f80c5db8736a (1 revision) (flutter/engine#39588) (flutter/flutter#120606) * 957494d9f Marks Linux_android flavors_test to be unflaky (flutter/flutter#120299) * 0792c2795 Roll Flutter Engine from fb8840578156 to df0ffe42b33e (2 revisions) (flutter/flutter#120607) * 7bacc25ee Remove accentColorBrightness usage (flutter/flutter#120577) * 98576cef5 Avoid null terminating characters in strings from Utf8FromUtf16() (flutter/flutter#109729) * 00adf9a33 roll packages (flutter/flutter#120609) * 2df140f40 Remove references to Observatory (flutter/flutter#118577) * a819d6156 Remove `prefer_equal_for_default_values` lint rule (flutter/flutter#120533) * 73afc7ba3 Roll Flutter Engine from df0ffe42b33e to 97dcf3e6201e (4 revisions) (flutter/flutter#120617) * f858302a6 Remove `brightness` from `AppBar`/`SliverAppBar`/`AppBarTheme`/`AppBarTheme.copyWith` (flutter/flutter#120575) * 95fd821ab Force `Mac build_tests` to run on x64 bots (flutter/flutter#120620) * ed35c80d2 d28cbf402 [Impeller] Return entity from filters instead of a snapshot (flutter/engine#39560) (flutter/flutter#120625) * 778b3fa32 support updating dragDecices at runtime (flutter/flutter#120336) * ddebe833b Added integration test for wide gamut support. (flutter/flutter#119657) * becb6bd00 Fix message type inconsistency between locales (flutter/flutter#120129) * f4495f5d3 Roll Flutter Engine from d28cbf402904 to 31a4648cbe99 (2 revisions) (flutter/flutter#120630) * 402caec2e Fix `ListTile`'s default `iconColor` token used & update examples (flutter/flutter#120444) * 865422da2 Force `Mac tool_integration_tests` to run on x64 bots (flutter/flutter#120634) * 07c548c69 Apply BindingBase.checkInstance to TestDefaultBinaryMessengerBinding (flutter/flutter#116937) * b08cc8be7 Roll Flutter Engine from 31a4648cbe99 to c4f51bc78644 (7 revisions) (flutter/flutter#120656) * 6a94f25a9 Roll Flutter Engine from c4f51bc78644 to 17ab09d382e3 (5 revisions) (flutter/flutter#120664) * b0edf5829 Roll Flutter Engine from 17ab09d382e3 to cbb7fc020b00 (2 revisions) (flutter/flutter#120673) * 17b4c70ff [M3] Add customizable overflow property to Snackbar's action (flutter/flutter#120394) * b35e4a54f Roll Plugins from f3bc6f1eb0c2 to 9c312d4d2f5f (2 revisions) (flutter/flutter#120694) * 9fd34048f f4fcb911b Roll Skia from bb7b22f3f444 to 8de7f68a3661 (1 revision) (flutter/engine#39619) (flutter/flutter#120699) * b9b4d3e43 roll packages (flutter/flutter#120628) * ed5bd1779 Fix tree by updating dependencies (flutter/flutter#120707) * 480c54c37 Increase Linux docs_publish timeout (flutter/flutter#120718) * c102bf467 [integration_test] Fix link to integration test for web section in `README.md` (flutter/flutter#103422) * 577ad2ee8 Fix error when resetting configurations in tear down phase (flutter/flutter#114468) * 2cfca820a Force Mac plugin_test to run on x64 bots (flutter/flutter#120714) * fd2fd94e3 d86089252 Roll Fuchsia Mac SDK from OeUljRQOmJwgDhNOo... to EFcCpAxOuQllDqP0F... (flutter/engine#39621) (flutter/flutter#120702) * 9d94a51b6 Move linux-x64-flutter-gtk.zip to linux-x64-debug location. (flutter/flutter#120658) * d29668ddb Improve network resources doctor check (flutter/flutter#120417) * 378668db4 added MaterialStateColor support to TabBarTheme.labelColor (flutter/flutter#109541) * ba46cb8d5 Remove deprecated AppBar.color & AppBar.backwardsCompatibility (flutter/flutter#120618) * 5a3957f3b Revert "Fix error when resetting configurations in tear down phase" (flutter/flutter#120739) * fd01812f6 Add temporary default case to support new PointerSignalKind (flutter/flutter#120731) * 4b8ad1b00 Temporarily disable info-based analyzer unit tests. (flutter/flutter#120753) * 911b13784 Roll Flutter Engine from d860892528ff to 44e36c9c0d73 (20 revisions) (flutter/flutter#120761) * 4ae5252f8 Fix license page crash (flutter/flutter#120728) * 31c73fcfe Roll Flutter Engine from 44e36c9c0d73 to bf7d51586704 (2 revisions) (flutter/flutter#120772) * 624445a45 Roll Flutter Engine from bf7d51586704 to ec70b5aa96be (2 revisions) (flutter/flutter#120781) * df41e58f6 1328c4bc6 Roll Dart SDK from 0456c4011cb3 to c022d475e9d8 (1 revision) (flutter/engine#39646) (flutter/flutter#120784) --- .ci/flutter_master.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/flutter_master.version b/.ci/flutter_master.version index cb3f9ddec296..776e76726092 100644 --- a/.ci/flutter_master.version +++ b/.ci/flutter_master.version @@ -1 +1 @@ -e3471f08d1d3f816fff8afd1ce9385dd3feb73d2 +df41e58f6f4e36475dee4949523f1ae3cb955dfc From cd09d9d3146f92ce2584408ea4d5702b2e835eef Mon Sep 17 00:00:00 2001 From: stuartmorgan Date: Thu, 16 Feb 2023 12:10:57 -0800 Subject: [PATCH 105/130] [ci] Update iOS simulator (#7131) Updates the iOS simulator used in CI from an iPhone 11 to an iPhone 13. Part of alignment with flutter/packages in preparation for merging repositories. Updates a Maps integration test for issues with the newer device. --- .ci/scripts/create_simulator.sh | 2 +- .ci/targets/ios_platform_tests.yaml | 2 +- .../example/lib/map_coordinates.dart | 36 ++++++++++--------- 3 files changed, 22 insertions(+), 18 deletions(-) diff --git a/.ci/scripts/create_simulator.sh b/.ci/scripts/create_simulator.sh index 3d86739051f1..98bfb6573593 100644 --- a/.ci/scripts/create_simulator.sh +++ b/.ci/scripts/create_simulator.sh @@ -3,7 +3,7 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -device=com.apple.CoreSimulator.SimDeviceType.iPhone-11 +device=com.apple.CoreSimulator.SimDeviceType.iPhone-13 os=com.apple.CoreSimulator.SimRuntime.iOS-16-0 xcrun simctl list diff --git a/.ci/targets/ios_platform_tests.yaml b/.ci/targets/ios_platform_tests.yaml index 5a00da7278a4..692b83dcb285 100644 --- a/.ci/targets/ios_platform_tests.yaml +++ b/.ci/targets/ios_platform_tests.yaml @@ -15,7 +15,7 @@ tasks: args: ["xcode-analyze", "--ios", "--ios-min-version=13.0"] - name: native test script: script/tool_runner.sh - args: ["native-test", "--ios", "--ios-destination", "platform=iOS Simulator,name=iPhone 11,OS=latest"] + args: ["native-test", "--ios", "--ios-destination", "platform=iOS Simulator,name=iPhone 13,OS=latest"] - name: drive examples # `drive-examples` contains integration tests, which changes the UI of the application. # This UI change sometimes affects `xctest`. diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/lib/map_coordinates.dart b/packages/google_maps_flutter/google_maps_flutter_ios/example/lib/map_coordinates.dart index 22f383bd1254..25247bc7c7bd 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/example/lib/map_coordinates.dart +++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/lib/map_coordinates.dart @@ -53,17 +53,28 @@ class _MapCoordinatesBodyState extends State<_MapCoordinatesBody> { _updateVisibleRegion(); return true; }, - child: ListView( + child: Stack( children: [ - Padding( - padding: const EdgeInsets.all(10.0), - child: Center( - child: SizedBox( - width: 300.0, - height: 200.0, - child: googleMap, + ListView( + children: [ + Padding( + padding: const EdgeInsets.all(10.0), + child: Center( + child: SizedBox( + width: 300.0, + height: 200.0, + child: googleMap, + ), + ), ), - ), + // Add a block at the bottom of this list to allow validation that the visible region of the map + // does not change when scrolled under the safe view on iOS. + // https://github.com/flutter/flutter/issues/107913 + const SizedBox( + width: 300, + height: 1000, + ), + ], ), if (mapController != null) Center( @@ -71,13 +82,6 @@ class _MapCoordinatesBodyState extends State<_MapCoordinatesBody> { '\nnortheast: ${_visibleRegion.northeast},' '\nsouthwest: ${_visibleRegion.southwest}'), ), - // Add a block at the bottom of this list to allow validation that the visible region of the map - // does not change when scrolled under the safe view on iOS. - // https://github.com/flutter/flutter/issues/107913 - const SizedBox( - width: 300, - height: 1000, - ), ], ), ); From 016c3b7f11c1142a3eb3e1783d3f4933805b16ef Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Thu, 16 Feb 2023 18:51:10 -0500 Subject: [PATCH 106/130] Roll Flutter from df41e58f6f4e to 22e17bb71050 (28 revisions) (#7186) * 3ad7ea3c9 Roll Plugins from 9c312d4d2f5f to 2ce625f1a87e (5 revisions) (flutter/flutter#120793) * 786571368 Roll Flutter Engine from 1328c4bc6299 to 4db9673d48d6 (2 revisions) (flutter/flutter#120796) * 541a8bfd9 Fix switching from scrollable and non-scrollable tab bars throws (flutter/flutter#120771) * ab1390e0a Use black30 for CupertinoTabBar's border (flutter/flutter#119509) * a513d4e7b Fix `flutter_localizations` README references (flutter/flutter#120800) * a664f08a5 In test of --(no-)fatal-infos analyzer flags, pin missing_return to info (flutter/flutter#120797) * ef49f5661 Add Android unit tests to plugin template (flutter/flutter#120720) * a12e242c0 Improve CupertinoContextMenu to match native more (flutter/flutter#117698) * a9f43665c Fix the `flutter run -d linux` tests (flutter/flutter#120721) * dff09558d 09da59a5a Roll Dart SDK from c022d475e9d8 to 5d17a336bdfe (1 revision) (flutter/engine#39649) (flutter/flutter#120816) * f35de0c80 Adds wide gamut saveLayer integration test (flutter/flutter#120131) * 99dcaa2d9 Roll Flutter Engine from 09da59a5adcf to a8b3d1af55b6 (3 revisions) (flutter/flutter#120821) * 8d150833b Use the impellerc GLES output flag when compiling shaders for Android (flutter/flutter#120647) * c6b636fa5 [flutter_tools] Replace Future.catchError() with Future.then(onError: ...) (flutter/flutter#120637) * 2b7d709fd Add `@widgetFactory` annotation (flutter/flutter#117455) * e65dfba8e Add Linux unit tests to plugin template (flutter/flutter#120814) * dccec41d5 5de007b90 Remove "bringup: true" from "Linux Fuchsia FEMU" (flutter/engine#39651) (flutter/flutter#120826) * d6de6bc68 9f3b061b7 Roll buildroot to 64b0c3deecaff8e66c2deb74e2171e8297b2bfcd (flutter/engine#39653) (flutter/flutter#120830) * da2508c9f bb1ff84b6 Add a white background to app anatomy diagram (flutter/engine#39638) (flutter/flutter#120832) * 1f85497ef [flutter_tools] Add the NoProfile parameter to the PowerShell execution statement (flutter/flutter#120786) * 4ad47fb47 Fix `StretchingOverscrollIndicator` not handling directional changes correctly (flutter/flutter#116548) * 9a721c456 Update AndroidManifest.xml.tmpl (flutter/flutter#120527) * c0b7d2ddd Roll Flutter Engine from bb1ff84b6c4f to 02a379db1d38 (4 revisions) (flutter/flutter#120845) * a10e295a0 Added identical(a,b) short circuit to Material Library lerp methods (flutter/flutter#120829) * efde35081 Roll Flutter Engine from 02a379db1d38 to a966cf878ffd (2 revisions) (flutter/flutter#120846) * cc473e4f1 Roll Flutter Engine from a966cf878ffd to 3fc40ca5beb9 (3 revisions) (flutter/flutter#120850) * d1252428c Roll Flutter Engine from 3fc40ca5beb9 to 9fa2a5c3cfbd (2 revisions) (flutter/flutter#120856) * 22e17bb71 ea1d087c4 Roll Skia from b8b36146c7a0 to 7b3fb04bc3d4 (3 revisions) (flutter/engine#39673) (flutter/flutter#120860) --- .ci/flutter_master.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/flutter_master.version b/.ci/flutter_master.version index 776e76726092..2369cb606e19 100644 --- a/.ci/flutter_master.version +++ b/.ci/flutter_master.version @@ -1 +1 @@ -df41e58f6f4e36475dee4949523f1ae3cb955dfc +22e17bb71050fb6bc18c95fede935fa4715772e8 From 7160f55e8f30e8487d9d8e4a51af4cc5c7e41a18 Mon Sep 17 00:00:00 2001 From: Jenn Magder Date: Thu, 16 Feb 2023 19:41:11 -0800 Subject: [PATCH 107/130] [ios_platform_images] Update minimum version to iOS 11 (#6874) * [ios_platform_images] Update minimum version to iOS 11 * README update --- packages/ios_platform_images/CHANGELOG.md | 4 ++++ packages/ios_platform_images/README.md | 6 +++--- .../example/ios/Flutter/AppFrameworkInfo.plist | 2 +- packages/ios_platform_images/example/ios/Podfile | 2 +- .../example/ios/Runner.xcodeproj/project.pbxproj | 13 ++++++++----- .../xcshareddata/xcschemes/Runner.xcscheme | 2 +- .../example/ios/Runner/Info.plist | 4 ++++ .../ios/ios_platform_images.podspec | 2 +- packages/ios_platform_images/pubspec.yaml | 4 ++-- 9 files changed, 25 insertions(+), 14 deletions(-) diff --git a/packages/ios_platform_images/CHANGELOG.md b/packages/ios_platform_images/CHANGELOG.md index 63f10450cfd7..72f1cf6d7d39 100644 --- a/packages/ios_platform_images/CHANGELOG.md +++ b/packages/ios_platform_images/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.2.2 + +* Updates minimum version to iOS 11. + ## 0.2.1+1 * Add lint ignore comments diff --git a/packages/ios_platform_images/README.md b/packages/ios_platform_images/README.md index 08dfc3e40b31..9265b108595e 100644 --- a/packages/ios_platform_images/README.md +++ b/packages/ios_platform_images/README.md @@ -8,9 +8,9 @@ Flutter images. When loading images from Image.xcassets the device specific variant is chosen ([iOS documentation](https://developer.apple.com/design/human-interface-guidelines/ios/icons-and-images/image-size-and-resolution/)). -| | iOS | -|-------------|------| -| **Support** | 9.0+ | +| | iOS | +|-------------|-------| +| **Support** | 11.0+ | ## Usage diff --git a/packages/ios_platform_images/example/ios/Flutter/AppFrameworkInfo.plist b/packages/ios_platform_images/example/ios/Flutter/AppFrameworkInfo.plist index f2872cf474ee..4f8d4d2456f3 100644 --- a/packages/ios_platform_images/example/ios/Flutter/AppFrameworkInfo.plist +++ b/packages/ios_platform_images/example/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 9.0 + 11.0 diff --git a/packages/ios_platform_images/example/ios/Podfile b/packages/ios_platform_images/example/ios/Podfile index 397864535f5d..fdcc671eb341 100644 --- a/packages/ios_platform_images/example/ios/Podfile +++ b/packages/ios_platform_images/example/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -# platform :ios, '9.0' +# platform :ios, '11.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/packages/ios_platform_images/example/ios/Runner.xcodeproj/project.pbxproj b/packages/ios_platform_images/example/ios/Runner.xcodeproj/project.pbxproj index 02e41bc13711..d6b4ef94bcef 100644 --- a/packages/ios_platform_images/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/ios_platform_images/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -219,7 +219,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1020; + LastUpgradeCheck = 1300; ORGANIZATIONNAME = "The Flutter Authors"; TargetAttributes = { 97C146ED1CF9000F007C117D = { @@ -278,10 +278,12 @@ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( @@ -332,6 +334,7 @@ }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -457,7 +460,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -539,7 +542,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -588,7 +591,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; diff --git a/packages/ios_platform_images/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/ios_platform_images/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 6de5fabfee04..7ae2cb4d4e54 100644 --- a/packages/ios_platform_images/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/ios_platform_images/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ UIViewControllerBasedStatusBarAppearance + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + diff --git a/packages/ios_platform_images/ios/ios_platform_images.podspec b/packages/ios_platform_images/ios/ios_platform_images.podspec index 3549277e9d86..02e5da149cd8 100644 --- a/packages/ios_platform_images/ios/ios_platform_images.podspec +++ b/packages/ios_platform_images/ios/ios_platform_images.podspec @@ -17,7 +17,7 @@ Downloaded by pub (not CocoaPods). s.documentation_url = 'https://pub.dev/packages/ios_platform_images' s.source_files = 'Classes/**/*' s.dependency 'Flutter' - s.platform = :ios, '9.0' + s.platform = :ios, '11.0' # Flutter.framework does not contain a i386 slice. Only x86_64 simulators are supported. s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } diff --git a/packages/ios_platform_images/pubspec.yaml b/packages/ios_platform_images/pubspec.yaml index 6a321fed4875..4193e3e339bf 100644 --- a/packages/ios_platform_images/pubspec.yaml +++ b/packages/ios_platform_images/pubspec.yaml @@ -2,10 +2,10 @@ name: ios_platform_images description: A plugin to share images between Flutter and iOS in add-to-app setups. repository: https://github.com/flutter/plugins/tree/main/packages/ios_platform_images issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+ios_platform_images%22 -version: 0.2.1+1 +version: 0.2.2 environment: - sdk: ">=2.14.0 <3.0.0" + sdk: '>=2.18.0 <3.0.0' flutter: ">=3.3.0" flutter: From ea048a249834801be707dca33fc3d754ce0b4969 Mon Sep 17 00:00:00 2001 From: Jenn Magder Date: Thu, 16 Feb 2023 19:41:12 -0800 Subject: [PATCH 108/130] [in_app_purchase] Update minimum Flutter version to 3.3 and iOS 11 (#6873) * [in_app_purchase] Bump minimum Flutter version to 3.3 for iOS plugins * Bump Flutter version * super params * Format --- .../in_app_purchase/CHANGELOG.md | 4 ++++ .../in_app_purchase/in_app_purchase/README.md | 6 ++--- .../ios/Flutter/AppFrameworkInfo.plist | 2 +- .../in_app_purchase/example/ios/Podfile | 2 +- .../ios/Runner.xcodeproj/project.pbxproj | 11 +++++---- .../xcshareddata/xcschemes/Runner.xcscheme | 2 +- .../example/ios/Runner/Info.plist | 4 ++++ .../in_app_purchase/pubspec.yaml | 2 +- .../in_app_purchase_storekit/CHANGELOG.md | 4 ++++ .../darwin/in_app_purchase_storekit.podspec | 2 +- .../ios/Flutter/AppFrameworkInfo.plist | 2 +- .../example/ios/Podfile | 2 +- .../ios/Runner.xcodeproj/project.pbxproj | 9 ++++--- .../example/ios/Runner/Info.plist | 4 ++++ .../shared/RunnerTests/TranslatorTests.m | 10 ++++---- .../src/types/app_store_product_details.dart | 24 +++++++------------ .../src/types/app_store_purchase_details.dart | 21 +++++++--------- .../src/types/app_store_purchase_param.dart | 9 +++---- .../in_app_purchase_storekit/pubspec.yaml | 6 ++--- 19 files changed, 65 insertions(+), 61 deletions(-) diff --git a/packages/in_app_purchase/in_app_purchase/CHANGELOG.md b/packages/in_app_purchase/in_app_purchase/CHANGELOG.md index 7279685afc5d..19e65372662d 100644 --- a/packages/in_app_purchase/in_app_purchase/CHANGELOG.md +++ b/packages/in_app_purchase/in_app_purchase/CHANGELOG.md @@ -1,3 +1,7 @@ +## 3.1.4 + +* Updates iOS minimum version in README. + ## 3.1.3 * Ignores a lint in the example app for backwards compatibility. diff --git a/packages/in_app_purchase/in_app_purchase/README.md b/packages/in_app_purchase/in_app_purchase/README.md index 91ca5233a2fc..6df0ebaccaa0 100644 --- a/packages/in_app_purchase/in_app_purchase/README.md +++ b/packages/in_app_purchase/in_app_purchase/README.md @@ -5,9 +5,9 @@ A storefront-independent API for purchases in Flutter apps. This plugin supports in-app purchases (_IAP_) through an _underlying store_, which can be the App Store (on iOS and macOS) or Google Play (on Android). -| | Android | iOS | macOS | -|-------------|---------|------|--------| -| **Support** | SDK 16+ | 9.0+ | 10.15+ | +| | Android | iOS | macOS | +|-------------|---------|-------|--------| +| **Support** | SDK 16+ | 11.0+ | 10.15+ |

CFBundleVersion 1.0 MinimumOSVersion - 8.0 + 11.0 diff --git a/packages/in_app_purchase/in_app_purchase/example/ios/Podfile b/packages/in_app_purchase/in_app_purchase/example/ios/Podfile index 310b9b498ba6..cad555de0518 100644 --- a/packages/in_app_purchase/in_app_purchase/example/ios/Podfile +++ b/packages/in_app_purchase/in_app_purchase/example/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -# platform :ios, '9.0' +# platform :ios, '11.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/packages/in_app_purchase/in_app_purchase/example/ios/Runner.xcodeproj/project.pbxproj b/packages/in_app_purchase/in_app_purchase/example/ios/Runner.xcodeproj/project.pbxproj index df13d20ae61d..8b83bba96707 100644 --- a/packages/in_app_purchase/in_app_purchase/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/in_app_purchase/in_app_purchase/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -176,7 +176,7 @@ isa = PBXProject; attributes = { DefaultBuildSystemTypeForWorkspace = Original; - LastUpgradeCheck = 1100; + LastUpgradeCheck = 1300; ORGANIZATIONNAME = "The Flutter Authors"; TargetAttributes = { 97C146ED1CF9000F007C117D = { @@ -224,10 +224,12 @@ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( @@ -256,6 +258,7 @@ }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -351,7 +354,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -401,7 +404,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/packages/in_app_purchase/in_app_purchase/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/in_app_purchase/in_app_purchase/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 3bb3697ef41c..50a8cfc99f50 100644 --- a/packages/in_app_purchase/in_app_purchase/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/in_app_purchase/in_app_purchase/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ UIViewControllerBasedStatusBarAppearance + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + diff --git a/packages/in_app_purchase/in_app_purchase/pubspec.yaml b/packages/in_app_purchase/in_app_purchase/pubspec.yaml index 443487465a27..483fe2c3b691 100644 --- a/packages/in_app_purchase/in_app_purchase/pubspec.yaml +++ b/packages/in_app_purchase/in_app_purchase/pubspec.yaml @@ -2,7 +2,7 @@ name: in_app_purchase description: A Flutter plugin for in-app purchases. Exposes APIs for making in-app purchases through the App Store and Google Play. repository: https://github.com/flutter/plugins/tree/main/packages/in_app_purchase/in_app_purchase issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+in_app_purchase%22 -version: 3.1.3 +version: 3.1.4 environment: sdk: ">=2.12.0 <3.0.0" diff --git a/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md b/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md index 569e0717fd38..6314bdc323f5 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md +++ b/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.3.6 + +* Updates minimum Flutter version to 3.3 and iOS 11. + ## 0.3.5+2 * Fix a crash when `appStoreReceiptURL` is nil. diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/in_app_purchase_storekit.podspec b/packages/in_app_purchase/in_app_purchase_storekit/darwin/in_app_purchase_storekit.podspec index 84c385e3405c..57a24bd674ab 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/in_app_purchase_storekit.podspec +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/in_app_purchase_storekit.podspec @@ -20,7 +20,7 @@ Downloaded by pub (not CocoaPods). s.public_header_files = 'Classes/**/*.h' s.ios.dependency 'Flutter' s.osx.dependency 'FlutterMacOS' - s.ios.deployment_target = '9.0' + s.ios.deployment_target = '11.0' s.osx.deployment_target = '10.15' s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } end diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Flutter/AppFrameworkInfo.plist b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Flutter/AppFrameworkInfo.plist index 8d4492f977ad..9625e105df39 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Flutter/AppFrameworkInfo.plist +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 9.0 + 11.0 diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Podfile b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Podfile index 5200b9fa5045..4f563887c820 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Podfile +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -# platform :ios, '9.0' +# platform :ios, '11.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner.xcodeproj/project.pbxproj b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner.xcodeproj/project.pbxproj index 3977d549af12..4b24d767a226 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 50; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -318,10 +318,12 @@ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( @@ -354,6 +356,7 @@ }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -493,7 +496,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -543,7 +546,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner/Info.plist b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner/Info.plist index a8f31ba92572..3c493732947a 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner/Info.plist +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner/Info.plist @@ -41,5 +41,9 @@ UIViewControllerBasedStatusBarAppearance + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/TranslatorTests.m b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/TranslatorTests.m index 34d686753762..6f77fa72a632 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/TranslatorTests.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/TranslatorTests.m @@ -224,12 +224,10 @@ - (void)testErrorWithUnsupportedUserInfo { } - (void)testLocaleToMap { - if (@available(iOS 10.0, *)) { - NSLocale *system = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]; - NSDictionary *map = [FIAObjectTranslator getMapFromNSLocale:system]; - XCTAssertEqualObjects(map[@"currencySymbol"], system.currencySymbol); - XCTAssertEqualObjects(map[@"countryCode"], system.countryCode); - } + NSLocale *system = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]; + NSDictionary *map = [FIAObjectTranslator getMapFromNSLocale:system]; + XCTAssertEqualObjects(map[@"currencySymbol"], system.currencySymbol); + XCTAssertEqualObjects(map[@"countryCode"], system.countryCode); } - (void)testSKStorefrontToMap { diff --git a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/types/app_store_product_details.dart b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/types/app_store_product_details.dart index a5d8c7287e3c..47bcf616fa40 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/types/app_store_product_details.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/types/app_store_product_details.dart @@ -12,23 +12,15 @@ class AppStoreProductDetails extends ProductDetails { /// Creates a new AppStore specific product details object with the provided /// details. AppStoreProductDetails({ - required String id, - required String title, - required String description, - required String price, - required double rawPrice, - required String currencyCode, + required super.id, + required super.title, + required super.description, + required super.price, + required super.rawPrice, + required super.currencyCode, required this.skProduct, - required String currencySymbol, - }) : super( - id: id, - title: title, - description: description, - price: price, - rawPrice: rawPrice, - currencyCode: currencyCode, - currencySymbol: currencySymbol, - ); + required super.currencySymbol, + }); /// Generate a [AppStoreProductDetails] object based on an iOS [SKProductWrapper] object. factory AppStoreProductDetails.fromSKProduct(SKProductWrapper product) { diff --git a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/types/app_store_purchase_details.dart b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/types/app_store_purchase_details.dart index 42cb225ede0a..21a1e11116b7 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/types/app_store_purchase_details.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/types/app_store_purchase_details.dart @@ -13,19 +13,14 @@ import '../store_kit_wrappers/enum_converters.dart'; class AppStorePurchaseDetails extends PurchaseDetails { /// Creates a new AppStore specific purchase details object with the provided /// details. - AppStorePurchaseDetails( - {String? purchaseID, - required String productID, - required PurchaseVerificationData verificationData, - required String? transactionDate, - required this.skPaymentTransaction, - required PurchaseStatus status}) - : super( - productID: productID, - purchaseID: purchaseID, - transactionDate: transactionDate, - verificationData: verificationData, - status: status) { + AppStorePurchaseDetails({ + super.purchaseID, + required super.productID, + required super.verificationData, + required super.transactionDate, + required this.skPaymentTransaction, + required PurchaseStatus status, + }) : super(status: status) { this.status = status; } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/types/app_store_purchase_param.dart b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/types/app_store_purchase_param.dart index 0e7e24166c4d..05096d3be40e 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/types/app_store_purchase_param.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/types/app_store_purchase_param.dart @@ -10,15 +10,12 @@ import '../../store_kit_wrappers.dart'; class AppStorePurchaseParam extends PurchaseParam { /// Creates a new [AppStorePurchaseParam] object with the given data. AppStorePurchaseParam({ - required ProductDetails productDetails, - String? applicationUserName, + required super.productDetails, + super.applicationUserName, this.quantity = 1, this.simulatesAskToBuyInSandbox = false, this.discount, - }) : super( - productDetails: productDetails, - applicationUserName: applicationUserName, - ); + }); /// Set it to `true` to produce an "ask to buy" flow for this payment in the /// sandbox. diff --git a/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml b/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml index 78ae8b16d524..5b734f4b630c 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml +++ b/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml @@ -2,11 +2,11 @@ name: in_app_purchase_storekit description: An implementation for the iOS and macOS platforms of the Flutter `in_app_purchase` plugin. This uses the StoreKit Framework. repository: https://github.com/flutter/plugins/tree/main/packages/in_app_purchase/in_app_purchase_storekit issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+in_app_purchase%22 -version: 0.3.5+2 +version: 0.3.6 environment: - sdk: ">=2.14.0 <3.0.0" - flutter: ">=3.0.0" + sdk: '>=2.18.0 <3.0.0' + flutter: ">=3.3.0" flutter: plugin: From 5304424565f9eead2b83bea987904ae00b6830a7 Mon Sep 17 00:00:00 2001 From: David Iglesias Date: Thu, 16 Feb 2023 20:05:23 -0800 Subject: [PATCH 109/130] [google_sign_in_web] Migrate to the GIS SDK. (#6921) * [google_sign_in_web] Migrate to GIS SDK. * include_granted_scopes in requestScopes call. * Remove the old JS-interop layer. * Introduce a mockable GisSdkClient for tests. * Split the people utils. * Delete tests for the old code. * Add some tests for the new code. * More utils_test.dart * Make jsifyAs reusable. * Ignore the tester in utils_test.dart * Make Clients overridable, and some renaming. * Test people.dart * Make autoDetectedClientId more testable. * Add mockito. * Comment about where to better split the code so GisSdkClient is testable too. * Add google_sign_in_web_test.dart (and its mocks) * dart format * Log only in debug. * Sync min sdk with package gis_web * Add migration notes to the README. * When the user is known upon signIn, remove friction. * Do not ask for user selection again in the authorization popup * Pass the email of the known user as a hint to the signIn method * Address PR comments / checks. * Update migration guide after comments from testers. * Update README.md * Remove package:jose from tests. * Rename to Vincent Adultman * _isJsSdkLoaded -> _jsSdkLoadedFuture * Remove idToken comment. * Link issue to split mocking better. * Remove dependency in package:jwt_decoder * Remove unneeded cast call. --- .../google_sign_in_web/CHANGELOG.md | 5 +- .../google_sign_in_web/README.md | 158 ++++-- .../google_sign_in_web/example/build.yaml | 6 + .../auth2_legacy_init_test.dart | 223 -------- .../example/integration_test/auth2_test.dart | 230 -------- .../gapi_load_legacy_init_test.dart | 51 -- .../integration_test/gapi_load_test.dart | 50 -- .../gapi_mocks/gapi_mocks.dart | 13 - .../gapi_mocks/src/auth2_init.dart | 109 ---- .../integration_test/gapi_mocks/src/gapi.dart | 12 - .../gapi_mocks/src/google_user.dart | 30 -- .../gapi_mocks/src/test_iife.dart | 15 - .../integration_test/gapi_utils_test.dart | 70 --- .../google_sign_in_web_test.dart | 219 ++++++++ .../google_sign_in_web_test.mocks.dart | 125 +++++ .../example/integration_test/people_test.dart | 132 +++++ .../example/integration_test/src/dom.dart | 59 +++ .../integration_test/src/jsify_as.dart | 10 + .../integration_test/src/jwt_examples.dart | 46 ++ .../example/integration_test/src/person.dart | 66 +++ .../integration_test/src/test_utils.dart | 10 - .../example/integration_test/utils_test.dart | 173 ++++++ .../google_sign_in_web/example/pubspec.yaml | 3 + .../google_sign_in_web/example/regen_mocks.sh | 10 + .../google_sign_in_web/example/run_test.sh | 7 +- .../lib/google_sign_in_web.dart | 178 +++---- .../lib/src/gis_client.dart | 310 +++++++++++ .../lib/src/js_interop/gapi.dart | 56 -- .../lib/src/js_interop/gapiauth2.dart | 497 ------------------ .../google_sign_in_web/lib/src/load_gapi.dart | 59 --- .../google_sign_in_web/lib/src/people.dart | 152 ++++++ .../google_sign_in_web/lib/src/utils.dart | 112 ++-- .../google_sign_in_web/pubspec.yaml | 6 +- 33 files changed, 1588 insertions(+), 1614 deletions(-) create mode 100644 packages/google_sign_in/google_sign_in_web/example/build.yaml delete mode 100644 packages/google_sign_in/google_sign_in_web/example/integration_test/auth2_legacy_init_test.dart delete mode 100644 packages/google_sign_in/google_sign_in_web/example/integration_test/auth2_test.dart delete mode 100644 packages/google_sign_in/google_sign_in_web/example/integration_test/gapi_load_legacy_init_test.dart delete mode 100644 packages/google_sign_in/google_sign_in_web/example/integration_test/gapi_load_test.dart delete mode 100644 packages/google_sign_in/google_sign_in_web/example/integration_test/gapi_mocks/gapi_mocks.dart delete mode 100644 packages/google_sign_in/google_sign_in_web/example/integration_test/gapi_mocks/src/auth2_init.dart delete mode 100644 packages/google_sign_in/google_sign_in_web/example/integration_test/gapi_mocks/src/gapi.dart delete mode 100644 packages/google_sign_in/google_sign_in_web/example/integration_test/gapi_mocks/src/google_user.dart delete mode 100644 packages/google_sign_in/google_sign_in_web/example/integration_test/gapi_mocks/src/test_iife.dart delete mode 100644 packages/google_sign_in/google_sign_in_web/example/integration_test/gapi_utils_test.dart create mode 100644 packages/google_sign_in/google_sign_in_web/example/integration_test/google_sign_in_web_test.dart create mode 100644 packages/google_sign_in/google_sign_in_web/example/integration_test/google_sign_in_web_test.mocks.dart create mode 100644 packages/google_sign_in/google_sign_in_web/example/integration_test/people_test.dart create mode 100644 packages/google_sign_in/google_sign_in_web/example/integration_test/src/dom.dart create mode 100644 packages/google_sign_in/google_sign_in_web/example/integration_test/src/jsify_as.dart create mode 100644 packages/google_sign_in/google_sign_in_web/example/integration_test/src/jwt_examples.dart create mode 100644 packages/google_sign_in/google_sign_in_web/example/integration_test/src/person.dart delete mode 100644 packages/google_sign_in/google_sign_in_web/example/integration_test/src/test_utils.dart create mode 100644 packages/google_sign_in/google_sign_in_web/example/integration_test/utils_test.dart create mode 100755 packages/google_sign_in/google_sign_in_web/example/regen_mocks.sh create mode 100644 packages/google_sign_in/google_sign_in_web/lib/src/gis_client.dart delete mode 100644 packages/google_sign_in/google_sign_in_web/lib/src/js_interop/gapi.dart delete mode 100644 packages/google_sign_in/google_sign_in_web/lib/src/js_interop/gapiauth2.dart delete 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/people.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 index 85c46da8facc..015334d77a59 100644 --- a/packages/google_sign_in/google_sign_in_web/CHANGELOG.md +++ b/packages/google_sign_in/google_sign_in_web/CHANGELOG.md @@ -1,5 +1,8 @@ -## NEXT +## 0.11.0 +* **Breaking Change:** Migrates JS-interop to `package:google_identity_services_web` + * Uses the new Google Identity Authentication and Authorization JS SDKs. [Docs](https://developers.google.com/identity). + * Added "Migrating to v0.11" section to the `README.md`. * Updates minimum Flutter version to 3.0. ## 0.10.2+1 diff --git a/packages/google_sign_in/google_sign_in_web/README.md b/packages/google_sign_in/google_sign_in_web/README.md index 7c02379808da..64bfd7a20161 100644 --- a/packages/google_sign_in/google_sign_in_web/README.md +++ b/packages/google_sign_in/google_sign_in_web/README.md @@ -2,6 +2,122 @@ The web implementation of [google_sign_in](https://pub.dev/packages/google_sign_in) +## Migrating to v0.11 (Google Identity Services) + +The `google_sign_in_web` plugin is backed by the new Google Identity Services +(GIS) JS SDK since version 0.11.0. + +The GIS SDK is used both for [Authentication](https://developers.google.com/identity/gsi/web/guides/overview) +and [Authorization](https://developers.google.com/identity/oauth2/web/guides/overview) flows. + +The GIS SDK, however, doesn't behave exactly like the one being deprecated. +Some concepts have experienced pretty drastic changes, and that's why this +plugin required a major version update. + +### Differences between Google Identity Services SDK and Google Sign-In for Web SDK. + +The **Google Sign-In JavaScript for Web JS SDK** is set to be deprecated after +March 31, 2023. **Google Identity Services (GIS) SDK** is the new solution to +quickly and easily sign users into your app suing their Google accounts. + +* In the GIS SDK, Authentication and Authorization are now two separate concerns. + * Authentication (information about the current user) flows will not + authorize `scopes` anymore. + * Authorization (permissions for the app to access certain user information) + flows will not return authentication information. +* The GIS SDK no longer has direct access to previously-seen users upon initialization. + * `signInSilently` now displays the One Tap UX for web. +* The GIS SDK only provides an `idToken` (JWT-encoded info) when the user + successfully completes an authentication flow. In the plugin: `signInSilently`. +* The plugin `signIn` method uses the Oauth "Implicit Flow" to Authorize the requested `scopes`. + * If the user hasn't `signInSilently`, they'll have to sign in as a first step + of the Authorization popup flow. + * If `signInSilently` was unsuccessful, the plugin will add extra `scopes` to + `signIn` and retrieve basic Profile information from the People API via a + REST call immediately after a successful authorization. In this case, the + `idToken` field of the `GoogleSignInUserData` will always be null. +* The GIS SDK no longer handles sign-in state and user sessions, it only provides + Authentication credentials for the moment the user did authenticate. +* The GIS SDK no longer is able to renew Authorization sessions on the web. + Once the token expires, API requests will begin to fail with unauthorized, + and user Authorization is required again. + +See more differences in the following migration guides: + +* Authentication > [Migrating from Google Sign-In](https://developers.google.com/identity/gsi/web/guides/migration) +* Authorization > [Migrate to Google Identity Services](https://developers.google.com/identity/oauth2/web/guides/migration-to-gis) + +### New use cases to take into account in your app + +#### Enable access to the People API for your GCP project + +Since the GIS SDK is separating Authentication from Authorization, the +[Oauth Implicit pop-up flow](https://developers.google.com/identity/oauth2/web/guides/use-token-model) +used to Authorize scopes does **not** return any Authentication information +anymore (user credential / `idToken`). + +If the plugin is not able to Authenticate an user from `signInSilently` (the +OneTap UX flow), it'll add extra `scopes` to those requested by the programmer +so it can perform a [People API request](https://developers.google.com/people/api/rest/v1/people/get) +to retrieve basic profile information about the user that is signed-in. + +The information retrieved from the People API is used to complete data for the +[`GoogleSignInAccount`](https://pub.dev/documentation/google_sign_in/latest/google_sign_in/GoogleSignInAccount-class.html) +object that is returned after `signIn` completes successfully. + +#### `signInSilently` always returns `null` + +Previous versions of this plugin were able to return a `GoogleSignInAccount` +object that was fully populated (signed-in and authorized) from `signInSilently` +because the former SDK equated "is authenticated" and "is authorized". + +With the GIS SDK, `signInSilently` only deals with user Authentication, so users +retrieved "silently" will only contain an `idToken`, but not an `accessToken`. + +Only after `signIn` or `requestScopes`, a user will be fully formed. + +The GIS-backed plugin always returns `null` from `signInSilently`, to force apps +that expect the former logic to perform a full `signIn`, which will result in a +fully Authenticated and Authorized user, and making this migration easier. + +#### `idToken` is `null` in the `GoogleSignInAccount` object after `signIn` + +Since the GIS SDK is separating Authentication and Authorization, when a user +fails to Authenticate through `signInSilently` and the plugin performs the +fallback request to the People API described above, +the returned `GoogleSignInUserData` object will contain basic profile information +(name, email, photo, ID), but its `idToken` will be `null`. + +This is because JWT are cryptographically signed by Google Identity Services, and +this plugin won't spoof that signature when it retrieves the information from a +simple REST request. + +#### User Sessions + +Since the GIS SDK does _not_ manage user sessions anymore, apps that relied on +this feature might break. + +If long-lived sessions are required, consider using some user authentication +system that supports Google Sign In as a federated Authentication provider, +like [Firebase Auth](https://firebase.google.com/docs/auth/flutter/federated-auth#google), +or similar. + +#### Expired / Invalid Authorization Tokens + +Since the GIS SDK does _not_ auto-renew authorization tokens anymore, it's now +the responsibility of your app to do so. + +Apps now need to monitor the status code of their REST API requests for response +codes different to `200`. For example: + +* `401`: Missing or invalid access token. +* `403`: Expired access token. + +In either case, your app needs to prompt the end user to `signIn` or +`requestScopes`, to interactively renew the token. + +The GIS SDK limits authorization token duration to one hour (3600 seconds). + ## Usage ### Import the package @@ -12,7 +128,7 @@ normally. This package will be automatically included in your app when you do. ### 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. +First, go through the instructions [here](https://developers.google.com/identity/gsi/web/guides/get-google-api-clientid) 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: @@ -29,7 +145,10 @@ You can do this by: 2. Clicking "Edit" in the OAuth 2.0 Web application client that you created above. 3. Adding the URIs you want to the **Authorized JavaScript origins**. -For local development, may add a `localhost` entry, for example: `http://localhost:7357` +For local development, you must add two `localhost` entries: + +* `http://localhost` and +* `http://localhost:7357` (or any port that is free in your machine) #### Starting flutter in http://localhost:7357 @@ -45,40 +164,11 @@ flutter run -d chrome --web-hostname localhost --web-port 7357 Read the rest of the instructions if you need to add extra APIs (like Google People API). - ### Using 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). - -Note that the `serverClientId` parameter of the `GoogleSignIn` constructor is not supported on Web. +See the [**Usage** instructions of `package:google_sign_in`](https://pub.dev/packages/google_sign_in#usage) -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); - } -} -``` +Note that the **`serverClientId` parameter of the `GoogleSignIn` constructor is not supported on Web.** ## Example @@ -86,7 +176,7 @@ Find the example wiring in the [Google sign-in example application](https://gith ## API details -See the [google_sign_in.dart](https://github.com/flutter/plugins/blob/main/packages/google_sign_in/google_sign_in/lib/google_sign_in.dart) for more API details. +See [google_sign_in.dart](https://github.com/flutter/plugins/blob/main/packages/google_sign_in/google_sign_in/lib/google_sign_in.dart) for more API details. ## Contributions and Testing diff --git a/packages/google_sign_in/google_sign_in_web/example/build.yaml b/packages/google_sign_in/google_sign_in_web/example/build.yaml new file mode 100644 index 000000000000..db3104bb04c6 --- /dev/null +++ b/packages/google_sign_in/google_sign_in_web/example/build.yaml @@ -0,0 +1,6 @@ +targets: + $default: + sources: + - integration_test/*.dart + - lib/$lib$ + - $package$ diff --git a/packages/google_sign_in/google_sign_in_web/example/integration_test/auth2_legacy_init_test.dart b/packages/google_sign_in/google_sign_in_web/example/integration_test/auth2_legacy_init_test.dart deleted file mode 100644 index 5dada90397fa..000000000000 --- a/packages/google_sign_in/google_sign_in_web/example/integration_test/auth2_legacy_init_test.dart +++ /dev/null @@ -1,223 +0,0 @@ -// Copyright 2013 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. - -// This file is a copy of `auth2_test.dart`, before it was migrated to the -// new `initWithParams` method, and is kept to ensure test coverage of the -// deprecated `init` method, until it is removed. - -import 'dart:html' as html; - -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_web/google_sign_in_web.dart'; -import 'package:integration_test/integration_test.dart'; -import 'package:js/js_util.dart' as js_util; - -import 'gapi_mocks/gapi_mocks.dart' as gapi_mocks; -import 'src/test_utils.dart'; - -void main() { - IntegrationTestWidgetsFlutterBinding.ensureInitialized(); - - final GoogleSignInTokenData expectedTokenData = - GoogleSignInTokenData(idToken: '70k3n', accessToken: 'access_70k3n'); - - final GoogleSignInUserData expectedUserData = GoogleSignInUserData( - displayName: 'Foo Bar', - email: 'foo@example.com', - id: '123', - photoUrl: 'http://example.com/img.jpg', - idToken: expectedTokenData.idToken, - ); - - late GoogleSignInPlugin plugin; - - group('plugin.initialize() throws a catchable exception', () { - setUp(() { - // The pre-configured use case for the instances of the plugin in this test - gapiUrl = toBase64Url(gapi_mocks.auth2InitError()); - plugin = GoogleSignInPlugin(); - }); - - testWidgets('initialize throws PlatformException', - (WidgetTester tester) async { - await expectLater( - plugin.init( - hostedDomain: 'foo', - scopes: ['some', 'scope'], - clientId: '1234', - ), - throwsA(isA())); - }); - - testWidgets('initialize forwards error code from JS', - (WidgetTester tester) async { - try { - await plugin.init( - hostedDomain: 'foo', - scopes: ['some', 'scope'], - clientId: '1234', - ); - fail('plugin.initialize should have thrown an exception!'); - } catch (e) { - final String code = js_util.getProperty(e, 'code'); - expect(code, 'idpiframe_initialization_failed'); - } - }); - }); - - group('other methods also throw catchable exceptions on initialize fail', () { - // This function ensures that initialize gets called, but for some reason, - // we ignored that it has thrown stuff... - Future discardInit() async { - try { - await plugin.init( - hostedDomain: 'foo', - scopes: ['some', 'scope'], - clientId: '1234', - ); - } catch (e) { - // Noop so we can call other stuff - } - } - - setUp(() { - gapiUrl = toBase64Url(gapi_mocks.auth2InitError()); - plugin = GoogleSignInPlugin(); - }); - - testWidgets('signInSilently throws', (WidgetTester tester) async { - await discardInit(); - await expectLater( - plugin.signInSilently(), throwsA(isA())); - }); - - testWidgets('signIn throws', (WidgetTester tester) async { - await discardInit(); - await expectLater(plugin.signIn(), throwsA(isA())); - }); - - testWidgets('getTokens throws', (WidgetTester tester) async { - await discardInit(); - await expectLater(plugin.getTokens(email: 'test@example.com'), - throwsA(isA())); - }); - testWidgets('requestScopes', (WidgetTester tester) async { - await discardInit(); - await expectLater(plugin.requestScopes(['newScope']), - throwsA(isA())); - }); - }); - - group('auth2 Init Successful', () { - setUp(() { - // The pre-configured use case for the instances of the plugin in this test - gapiUrl = toBase64Url(gapi_mocks.auth2InitSuccess(expectedUserData)); - plugin = GoogleSignInPlugin(); - }); - - testWidgets('Init requires clientId', (WidgetTester tester) async { - expect(plugin.init(hostedDomain: ''), throwsAssertionError); - }); - - testWidgets("Init doesn't accept spaces in scopes", - (WidgetTester tester) async { - expect( - plugin.init( - hostedDomain: '', - clientId: '', - scopes: ['scope with spaces'], - ), - throwsAssertionError); - }); - - // See: https://github.com/flutter/flutter/issues/88084 - testWidgets('Init passes plugin_name parameter with the expected value', - (WidgetTester tester) async { - await plugin.init( - hostedDomain: 'foo', - scopes: ['some', 'scope'], - clientId: '1234', - ); - - final Object? initParameters = - js_util.getProperty(html.window, 'gapi2.init.parameters'); - expect(initParameters, isNotNull); - - final Object? pluginNameParameter = - js_util.getProperty(initParameters!, 'plugin_name'); - expect(pluginNameParameter, isA()); - expect(pluginNameParameter, 'dart-google_sign_in_web'); - }); - - group('Successful .initialize, then', () { - setUp(() async { - await plugin.init( - hostedDomain: 'foo', - scopes: ['some', 'scope'], - clientId: '1234', - ); - await plugin.initialized; - }); - - testWidgets('signInSilently', (WidgetTester tester) async { - final GoogleSignInUserData actualUser = - (await plugin.signInSilently())!; - - expect(actualUser, expectedUserData); - }); - - testWidgets('signIn', (WidgetTester tester) async { - final GoogleSignInUserData actualUser = (await plugin.signIn())!; - - expect(actualUser, expectedUserData); - }); - - testWidgets('getTokens', (WidgetTester tester) async { - final GoogleSignInTokenData actualToken = - await plugin.getTokens(email: expectedUserData.email); - - expect(actualToken, expectedTokenData); - }); - - testWidgets('requestScopes', (WidgetTester tester) async { - final bool scopeGranted = - await plugin.requestScopes(['newScope']); - - expect(scopeGranted, isTrue); - }); - }); - }); - - group('auth2 Init successful, but exception on signIn() method', () { - setUp(() async { - // The pre-configured use case for the instances of the plugin in this test - gapiUrl = toBase64Url(gapi_mocks.auth2SignInError()); - plugin = GoogleSignInPlugin(); - await plugin.init( - hostedDomain: 'foo', - scopes: ['some', 'scope'], - clientId: '1234', - ); - await plugin.initialized; - }); - - testWidgets('User aborts sign in flow, throws PlatformException', - (WidgetTester tester) async { - await expectLater(plugin.signIn(), throwsA(isA())); - }); - - testWidgets('User aborts sign in flow, error code is forwarded from JS', - (WidgetTester tester) async { - try { - await plugin.signIn(); - fail('plugin.signIn() should have thrown an exception!'); - } catch (e) { - final String code = js_util.getProperty(e, 'code'); - expect(code, 'popup_closed_by_user'); - } - }); - }); -} diff --git a/packages/google_sign_in/google_sign_in_web/example/integration_test/auth2_test.dart b/packages/google_sign_in/google_sign_in_web/example/integration_test/auth2_test.dart deleted file mode 100644 index 3e803b83fa0c..000000000000 --- a/packages/google_sign_in/google_sign_in_web/example/integration_test/auth2_test.dart +++ /dev/null @@ -1,230 +0,0 @@ -// Copyright 2013 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. - -import 'dart:html' as html; - -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_web/google_sign_in_web.dart'; -import 'package:integration_test/integration_test.dart'; -import 'package:js/js_util.dart' as js_util; - -import 'gapi_mocks/gapi_mocks.dart' as gapi_mocks; -import 'src/test_utils.dart'; - -void main() { - IntegrationTestWidgetsFlutterBinding.ensureInitialized(); - - final GoogleSignInTokenData expectedTokenData = - GoogleSignInTokenData(idToken: '70k3n', accessToken: 'access_70k3n'); - - final GoogleSignInUserData expectedUserData = GoogleSignInUserData( - displayName: 'Foo Bar', - email: 'foo@example.com', - id: '123', - photoUrl: 'http://example.com/img.jpg', - idToken: expectedTokenData.idToken, - ); - - late GoogleSignInPlugin plugin; - - group('plugin.initWithParams() throws a catchable exception', () { - setUp(() { - // The pre-configured use case for the instances of the plugin in this test - gapiUrl = toBase64Url(gapi_mocks.auth2InitError()); - plugin = GoogleSignInPlugin(); - }); - - testWidgets('throws PlatformException', (WidgetTester tester) async { - await expectLater( - plugin.initWithParams(const SignInInitParameters( - hostedDomain: 'foo', - scopes: ['some', 'scope'], - clientId: '1234', - )), - throwsA(isA())); - }); - - testWidgets('forwards error code from JS', (WidgetTester tester) async { - try { - await plugin.initWithParams(const SignInInitParameters( - hostedDomain: 'foo', - scopes: ['some', 'scope'], - clientId: '1234', - )); - fail('plugin.initWithParams should have thrown an exception!'); - } catch (e) { - final String code = js_util.getProperty(e, 'code'); - expect(code, 'idpiframe_initialization_failed'); - } - }); - }); - - group('other methods also throw catchable exceptions on initWithParams fail', - () { - // This function ensures that initWithParams gets called, but for some - // reason, we ignored that it has thrown stuff... - Future discardInit() async { - try { - await plugin.initWithParams(const SignInInitParameters( - hostedDomain: 'foo', - scopes: ['some', 'scope'], - clientId: '1234', - )); - } catch (e) { - // Noop so we can call other stuff - } - } - - setUp(() { - gapiUrl = toBase64Url(gapi_mocks.auth2InitError()); - plugin = GoogleSignInPlugin(); - }); - - testWidgets('signInSilently throws', (WidgetTester tester) async { - await discardInit(); - await expectLater( - plugin.signInSilently(), throwsA(isA())); - }); - - testWidgets('signIn throws', (WidgetTester tester) async { - await discardInit(); - await expectLater(plugin.signIn(), throwsA(isA())); - }); - - testWidgets('getTokens throws', (WidgetTester tester) async { - await discardInit(); - await expectLater(plugin.getTokens(email: 'test@example.com'), - throwsA(isA())); - }); - testWidgets('requestScopes', (WidgetTester tester) async { - await discardInit(); - await expectLater(plugin.requestScopes(['newScope']), - throwsA(isA())); - }); - }); - - group('auth2 Init Successful', () { - setUp(() { - // The pre-configured use case for the instances of the plugin in this test - gapiUrl = toBase64Url(gapi_mocks.auth2InitSuccess(expectedUserData)); - plugin = GoogleSignInPlugin(); - }); - - testWidgets('Init requires clientId', (WidgetTester tester) async { - expect( - plugin.initWithParams(const SignInInitParameters(hostedDomain: '')), - throwsAssertionError); - }); - - testWidgets("Init doesn't accept serverClientId", - (WidgetTester tester) async { - expect( - plugin.initWithParams(const SignInInitParameters( - clientId: '', - serverClientId: '', - )), - throwsAssertionError); - }); - - testWidgets("Init doesn't accept spaces in scopes", - (WidgetTester tester) async { - expect( - plugin.initWithParams(const SignInInitParameters( - hostedDomain: '', - clientId: '', - scopes: ['scope with spaces'], - )), - throwsAssertionError); - }); - - // See: https://github.com/flutter/flutter/issues/88084 - testWidgets('Init passes plugin_name parameter with the expected value', - (WidgetTester tester) async { - await plugin.initWithParams(const SignInInitParameters( - hostedDomain: 'foo', - scopes: ['some', 'scope'], - clientId: '1234', - )); - - final Object? initParameters = - js_util.getProperty(html.window, 'gapi2.init.parameters'); - expect(initParameters, isNotNull); - - final Object? pluginNameParameter = - js_util.getProperty(initParameters!, 'plugin_name'); - expect(pluginNameParameter, isA()); - expect(pluginNameParameter, 'dart-google_sign_in_web'); - }); - - group('Successful .initWithParams, then', () { - setUp(() async { - await plugin.initWithParams(const SignInInitParameters( - hostedDomain: 'foo', - scopes: ['some', 'scope'], - clientId: '1234', - )); - await plugin.initialized; - }); - - testWidgets('signInSilently', (WidgetTester tester) async { - final GoogleSignInUserData actualUser = - (await plugin.signInSilently())!; - - expect(actualUser, expectedUserData); - }); - - testWidgets('signIn', (WidgetTester tester) async { - final GoogleSignInUserData actualUser = (await plugin.signIn())!; - - expect(actualUser, expectedUserData); - }); - - testWidgets('getTokens', (WidgetTester tester) async { - final GoogleSignInTokenData actualToken = - await plugin.getTokens(email: expectedUserData.email); - - expect(actualToken, expectedTokenData); - }); - - testWidgets('requestScopes', (WidgetTester tester) async { - final bool scopeGranted = - await plugin.requestScopes(['newScope']); - - expect(scopeGranted, isTrue); - }); - }); - }); - - group('auth2 Init successful, but exception on signIn() method', () { - setUp(() async { - // The pre-configured use case for the instances of the plugin in this test - gapiUrl = toBase64Url(gapi_mocks.auth2SignInError()); - plugin = GoogleSignInPlugin(); - await plugin.initWithParams(const SignInInitParameters( - hostedDomain: 'foo', - scopes: ['some', 'scope'], - clientId: '1234', - )); - await plugin.initialized; - }); - - testWidgets('User aborts sign in flow, throws PlatformException', - (WidgetTester tester) async { - await expectLater(plugin.signIn(), throwsA(isA())); - }); - - testWidgets('User aborts sign in flow, error code is forwarded from JS', - (WidgetTester tester) async { - try { - await plugin.signIn(); - fail('plugin.signIn() should have thrown an exception!'); - } catch (e) { - final String code = js_util.getProperty(e, 'code'); - expect(code, 'popup_closed_by_user'); - } - }); - }); -} diff --git a/packages/google_sign_in/google_sign_in_web/example/integration_test/gapi_load_legacy_init_test.dart b/packages/google_sign_in/google_sign_in_web/example/integration_test/gapi_load_legacy_init_test.dart deleted file mode 100644 index 7bfef53f7a23..000000000000 --- a/packages/google_sign_in/google_sign_in_web/example/integration_test/gapi_load_legacy_init_test.dart +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2013 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. - -// This file is a copy of `gapi_load_test.dart`, before it was migrated to the -// new `initWithParams` method, and is kept to ensure test coverage of the -// deprecated `init` method, until it is removed. - -import 'dart:html' as html; - -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 'package:integration_test/integration_test.dart'; - -import 'gapi_mocks/gapi_mocks.dart' as gapi_mocks; -import 'src/test_utils.dart'; - -void main() { - IntegrationTestWidgetsFlutterBinding.ensureInitialized(); - - gapiUrl = toBase64Url(gapi_mocks.auth2InitSuccess( - GoogleSignInUserData(email: 'test@test.com', id: '1234'))); - - testWidgets('Plugin is initialized after GAPI fully loads and init is called', - (WidgetTester tester) 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', - ); - expect(() { - plugin.initialized; - }, throwsStateError, - reason: - 'The plugin should throw if checking for `initialized` before calling .init'); - await plugin.init(hostedDomain: '', clientId: ''); - 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/example/integration_test/gapi_load_test.dart b/packages/google_sign_in/google_sign_in_web/example/integration_test/gapi_load_test.dart deleted file mode 100644 index fc753e20d92c..000000000000 --- a/packages/google_sign_in/google_sign_in_web/example/integration_test/gapi_load_test.dart +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2013 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. - -import 'dart:html' as html; - -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 'package:integration_test/integration_test.dart'; - -import 'gapi_mocks/gapi_mocks.dart' as gapi_mocks; -import 'src/test_utils.dart'; - -void main() { - IntegrationTestWidgetsFlutterBinding.ensureInitialized(); - - gapiUrl = toBase64Url(gapi_mocks.auth2InitSuccess( - GoogleSignInUserData(email: 'test@test.com', id: '1234'))); - - testWidgets('Plugin is initialized after GAPI fully loads and init is called', - (WidgetTester tester) 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', - ); - expect(() { - plugin.initialized; - }, throwsStateError, - reason: 'The plugin should throw if checking for `initialized` before ' - 'calling .initWithParams'); - await plugin.initWithParams(const SignInInitParameters( - hostedDomain: '', - clientId: '', - )); - 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/example/integration_test/gapi_mocks/gapi_mocks.dart b/packages/google_sign_in/google_sign_in_web/example/integration_test/gapi_mocks/gapi_mocks.dart deleted file mode 100644 index 43eb9a55d06b..000000000000 --- a/packages/google_sign_in/google_sign_in_web/example/integration_test/gapi_mocks/gapi_mocks.dart +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright 2013 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. - -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/auth2_init.dart'; diff --git a/packages/google_sign_in/google_sign_in_web/example/integration_test/gapi_mocks/src/auth2_init.dart b/packages/google_sign_in/google_sign_in_web/example/integration_test/gapi_mocks/src/auth2_init.dart deleted file mode 100644 index 84f4e6ee8ba8..000000000000 --- a/packages/google_sign_in/google_sign_in_web/example/integration_test/gapi_mocks/src/auth2_init.dart +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright 2013 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. - -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) { - /*Leak the initOptions so we can look at them later.*/ - window['gapi2.init.parameters'] = initOptions; - return { - then: (onSuccess, onError) => { - window.setTimeout(() => { - onSuccess(window.gapi.auth2); - }, 30); - }, - 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(); -'''); - -String auth2InitError() => testIife(''' -${gapi()} - -function GapiAuth2() {} -GapiAuth2.prototype.init = function (initOptions) { - return { - then: (onSuccess, onError) => { - window.setTimeout(() => { - onError({ - error: 'idpiframe_initialization_failed', - details: 'This error was raised from a test.', - }); - }, 30); - } - } -}; - -window.gapi.auth2 = new GapiAuth2(); -'''); - -String auth2SignInError([String error = 'popup_closed_by_user']) => testIife(''' -${gapi()} - -var mockUser = null; - -function GapiAuth2() {} -GapiAuth2.prototype.init = function (initOptions) { - return { - then: (onSuccess, onError) => { - window.setTimeout(() => { - onSuccess(window.gapi.auth2); - }, 30); - }, - currentUser: { - listen: (cb) => { - window.setTimeout(() => { - cb(mockUser); - }, 30); - } - } - } -}; - -GapiAuth2.prototype.getAuthInstance = function () { - return { - signIn: () => { - return new Promise((resolve, reject) => { - window.setTimeout(() => { - reject({ - error: '$error' - }); - }, 30); - }); - }, - } -}; - -window.gapi.auth2 = new GapiAuth2(); -'''); diff --git a/packages/google_sign_in/google_sign_in_web/example/integration_test/gapi_mocks/src/gapi.dart b/packages/google_sign_in/google_sign_in_web/example/integration_test/gapi_mocks/src/gapi.dart deleted file mode 100644 index 0e652c647a38..000000000000 --- a/packages/google_sign_in/google_sign_in_web/example/integration_test/gapi_mocks/src/gapi.dart +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright 2013 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. - -// 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/example/integration_test/gapi_mocks/src/google_user.dart b/packages/google_sign_in/google_sign_in_web/example/integration_test/gapi_mocks/src/google_user.dart deleted file mode 100644 index e5e6eb262502..000000000000 --- a/packages/google_sign_in/google_sign_in_web/example/integration_test/gapi_mocks/src/google_user.dart +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2013 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. - -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}', - } - }, - getGrantedScopes: () => 'some scope', - grant: () => true, - isSignedIn: () => { - return ${data != null ? 'true' : 'false'}; - }, -} -'''; diff --git a/packages/google_sign_in/google_sign_in_web/example/integration_test/gapi_mocks/src/test_iife.dart b/packages/google_sign_in/google_sign_in_web/example/integration_test/gapi_mocks/src/test_iife.dart deleted file mode 100644 index c5aac367c1de..000000000000 --- a/packages/google_sign_in/google_sign_in_web/example/integration_test/gapi_mocks/src/test_iife.dart +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2013 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. - -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/example/integration_test/gapi_utils_test.dart b/packages/google_sign_in/google_sign_in_web/example/integration_test/gapi_utils_test.dart deleted file mode 100644 index b9daac44dba8..000000000000 --- a/packages/google_sign_in/google_sign_in_web/example/integration_test/gapi_utils_test.dart +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright 2013 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. - -import 'package:flutter_test/flutter_test.dart'; -import 'package:google_sign_in_web/src/js_interop/gapiauth2.dart' as gapi; -import 'package:google_sign_in_web/src/utils.dart'; -import 'package:integration_test/integration_test.dart'; - -void main() { - // The non-null use cases are covered by the auth2_test.dart file. - IntegrationTestWidgetsFlutterBinding.ensureInitialized(); - - group('gapiUserToPluginUserData', () { - late FakeGoogleUser fakeUser; - - setUp(() { - fakeUser = FakeGoogleUser(); - }); - - testWidgets('null user -> null response', (WidgetTester tester) async { - expect(gapiUserToPluginUserData(null), isNull); - }); - - testWidgets('not signed-in user -> null response', - (WidgetTester tester) async { - expect(gapiUserToPluginUserData(fakeUser), isNull); - }); - - testWidgets('signed-in, but null profile user -> null response', - (WidgetTester tester) async { - fakeUser.setIsSignedIn(true); - expect(gapiUserToPluginUserData(fakeUser), isNull); - }); - - testWidgets('signed-in, null userId in profile user -> null response', - (WidgetTester tester) async { - fakeUser.setIsSignedIn(true); - fakeUser.setBasicProfile(FakeBasicProfile()); - expect(gapiUserToPluginUserData(fakeUser), isNull); - }); - }); -} - -class FakeGoogleUser extends Fake implements gapi.GoogleUser { - bool _isSignedIn = false; - gapi.BasicProfile? _basicProfile; - - @override - bool isSignedIn() => _isSignedIn; - @override - gapi.BasicProfile? getBasicProfile() => _basicProfile; - - // ignore: use_setters_to_change_properties - void setIsSignedIn(bool isSignedIn) { - _isSignedIn = isSignedIn; - } - - // ignore: use_setters_to_change_properties - void setBasicProfile(gapi.BasicProfile basicProfile) { - _basicProfile = basicProfile; - } -} - -class FakeBasicProfile extends Fake implements gapi.BasicProfile { - String? _id; - - @override - String? getId() => _id; -} diff --git a/packages/google_sign_in/google_sign_in_web/example/integration_test/google_sign_in_web_test.dart b/packages/google_sign_in/google_sign_in_web/example/integration_test/google_sign_in_web_test.dart new file mode 100644 index 000000000000..3dcc192e8aaa --- /dev/null +++ b/packages/google_sign_in/google_sign_in_web/example/integration_test/google_sign_in_web_test.dart @@ -0,0 +1,219 @@ +// Copyright 2013 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. + +import 'package:flutter/services.dart' show PlatformException; +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 'package:google_sign_in_web/src/gis_client.dart'; +import 'package:google_sign_in_web/src/people.dart'; +import 'package:integration_test/integration_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart' as mockito; + +import 'google_sign_in_web_test.mocks.dart'; +import 'src/dom.dart'; +import 'src/person.dart'; + +// Mock GisSdkClient so we can simulate any response from the JS side. +@GenerateMocks([], customMocks: >[ + MockSpec(onMissingStub: OnMissingStub.returnDefault), +]) +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + group('Constructor', () { + const String expectedClientId = '3xp3c73d_c113n7_1d'; + + testWidgets('Loads clientId when set in a meta', (_) async { + final GoogleSignInPlugin plugin = GoogleSignInPlugin( + debugOverrideLoader: true, + ); + + expect(plugin.autoDetectedClientId, isNull); + + // Add it to the test page now, and try again + final DomHtmlMetaElement meta = + document.createElement('meta') as DomHtmlMetaElement + ..name = clientIdMetaName + ..content = expectedClientId; + + document.head.appendChild(meta); + + final GoogleSignInPlugin another = GoogleSignInPlugin( + debugOverrideLoader: true, + ); + + expect(another.autoDetectedClientId, expectedClientId); + + // cleanup + meta.remove(); + }); + }); + + group('initWithParams', () { + late GoogleSignInPlugin plugin; + late MockGisSdkClient mockGis; + + setUp(() { + plugin = GoogleSignInPlugin( + debugOverrideLoader: true, + ); + mockGis = MockGisSdkClient(); + }); + + testWidgets('initializes if all is OK', (_) async { + await plugin.initWithParams( + const SignInInitParameters( + clientId: 'some-non-null-client-id', + scopes: ['ok1', 'ok2', 'ok3'], + ), + overrideClient: mockGis, + ); + + expect(plugin.initialized, completes); + }); + + testWidgets('asserts clientId is not null', (_) async { + expect(() async { + await plugin.initWithParams( + const SignInInitParameters(), + overrideClient: mockGis, + ); + }, throwsAssertionError); + }); + + testWidgets('asserts serverClientId must be null', (_) async { + expect(() async { + await plugin.initWithParams( + const SignInInitParameters( + clientId: 'some-non-null-client-id', + serverClientId: 'unexpected-non-null-client-id', + ), + overrideClient: mockGis, + ); + }, throwsAssertionError); + }); + + testWidgets('asserts no scopes have any spaces', (_) async { + expect(() async { + await plugin.initWithParams( + const SignInInitParameters( + clientId: 'some-non-null-client-id', + scopes: ['ok1', 'ok2', 'not ok', 'ok3'], + ), + overrideClient: mockGis, + ); + }, throwsAssertionError); + }); + + testWidgets('must be called for most of the API to work', (_) async { + expect(() async { + await plugin.signInSilently(); + }, throwsStateError); + + expect(() async { + await plugin.signIn(); + }, throwsStateError); + + expect(() async { + await plugin.getTokens(email: ''); + }, throwsStateError); + + expect(() async { + await plugin.signOut(); + }, throwsStateError); + + expect(() async { + await plugin.disconnect(); + }, throwsStateError); + + expect(() async { + await plugin.isSignedIn(); + }, throwsStateError); + + expect(() async { + await plugin.clearAuthCache(token: ''); + }, throwsStateError); + + expect(() async { + await plugin.requestScopes([]); + }, throwsStateError); + }); + }); + + group('(with mocked GIS)', () { + late GoogleSignInPlugin plugin; + late MockGisSdkClient mockGis; + const SignInInitParameters options = SignInInitParameters( + clientId: 'some-non-null-client-id', + scopes: ['ok1', 'ok2', 'ok3'], + ); + + setUp(() { + plugin = GoogleSignInPlugin( + debugOverrideLoader: true, + ); + mockGis = MockGisSdkClient(); + }); + + group('signInSilently', () { + setUp(() { + plugin.initWithParams(options, overrideClient: mockGis); + }); + + testWidgets('always returns null, regardless of GIS response', (_) async { + final GoogleSignInUserData someUser = extractUserData(person)!; + + mockito + .when(mockGis.signInSilently()) + .thenAnswer((_) => Future.value(someUser)); + + expect(plugin.signInSilently(), completion(isNull)); + + mockito + .when(mockGis.signInSilently()) + .thenAnswer((_) => Future.value()); + + expect(plugin.signInSilently(), completion(isNull)); + }); + }); + + group('signIn', () { + setUp(() { + plugin.initWithParams(options, overrideClient: mockGis); + }); + + testWidgets('returns the signed-in user', (_) async { + final GoogleSignInUserData someUser = extractUserData(person)!; + + mockito + .when(mockGis.signIn()) + .thenAnswer((_) => Future.value(someUser)); + + expect(await plugin.signIn(), someUser); + }); + + testWidgets('returns null if no user is signed in', (_) async { + mockito + .when(mockGis.signIn()) + .thenAnswer((_) => Future.value()); + + expect(await plugin.signIn(), isNull); + }); + + testWidgets('converts inner errors to PlatformException', (_) async { + mockito.when(mockGis.signIn()).thenThrow('popup_closed'); + + try { + await plugin.signIn(); + fail('signIn should have thrown an exception'); + } catch (exception) { + expect(exception, isA()); + expect((exception as PlatformException).code, 'popup_closed'); + } + }); + }); + }); +} diff --git a/packages/google_sign_in/google_sign_in_web/example/integration_test/google_sign_in_web_test.mocks.dart b/packages/google_sign_in/google_sign_in_web/example/integration_test/google_sign_in_web_test.mocks.dart new file mode 100644 index 000000000000..b60dac9d4b95 --- /dev/null +++ b/packages/google_sign_in/google_sign_in_web/example/integration_test/google_sign_in_web_test.mocks.dart @@ -0,0 +1,125 @@ +// Mocks generated by Mockito 5.3.2 from annotations +// in google_sign_in_web_integration_tests/integration_test/google_sign_in_web_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i4; + +import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart' + as _i2; +import 'package:google_sign_in_web/src/gis_client.dart' as _i3; +import 'package:mockito/mockito.dart' as _i1; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +class _FakeGoogleSignInTokenData_0 extends _i1.SmartFake + implements _i2.GoogleSignInTokenData { + _FakeGoogleSignInTokenData_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +/// A class which mocks [GisSdkClient]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockGisSdkClient extends _i1.Mock implements _i3.GisSdkClient { + @override + _i4.Future<_i2.GoogleSignInUserData?> signInSilently() => (super.noSuchMethod( + Invocation.method( + #signInSilently, + [], + ), + returnValue: _i4.Future<_i2.GoogleSignInUserData?>.value(), + returnValueForMissingStub: + _i4.Future<_i2.GoogleSignInUserData?>.value(), + ) as _i4.Future<_i2.GoogleSignInUserData?>); + @override + _i4.Future<_i2.GoogleSignInUserData?> signIn() => (super.noSuchMethod( + Invocation.method( + #signIn, + [], + ), + returnValue: _i4.Future<_i2.GoogleSignInUserData?>.value(), + returnValueForMissingStub: + _i4.Future<_i2.GoogleSignInUserData?>.value(), + ) as _i4.Future<_i2.GoogleSignInUserData?>); + @override + _i2.GoogleSignInTokenData getTokens() => (super.noSuchMethod( + Invocation.method( + #getTokens, + [], + ), + returnValue: _FakeGoogleSignInTokenData_0( + this, + Invocation.method( + #getTokens, + [], + ), + ), + returnValueForMissingStub: _FakeGoogleSignInTokenData_0( + this, + Invocation.method( + #getTokens, + [], + ), + ), + ) as _i2.GoogleSignInTokenData); + @override + _i4.Future signOut() => (super.noSuchMethod( + Invocation.method( + #signOut, + [], + ), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) as _i4.Future); + @override + _i4.Future disconnect() => (super.noSuchMethod( + Invocation.method( + #disconnect, + [], + ), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) as _i4.Future); + @override + _i4.Future isSignedIn() => (super.noSuchMethod( + Invocation.method( + #isSignedIn, + [], + ), + returnValue: _i4.Future.value(false), + returnValueForMissingStub: _i4.Future.value(false), + ) as _i4.Future); + @override + _i4.Future clearAuthCache() => (super.noSuchMethod( + Invocation.method( + #clearAuthCache, + [], + ), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) as _i4.Future); + @override + _i4.Future requestScopes(List? scopes) => (super.noSuchMethod( + Invocation.method( + #requestScopes, + [scopes], + ), + returnValue: _i4.Future.value(false), + returnValueForMissingStub: _i4.Future.value(false), + ) as _i4.Future); +} diff --git a/packages/google_sign_in/google_sign_in_web/example/integration_test/people_test.dart b/packages/google_sign_in/google_sign_in_web/example/integration_test/people_test.dart new file mode 100644 index 000000000000..e81ccb6e95b5 --- /dev/null +++ b/packages/google_sign_in/google_sign_in_web/example/integration_test/people_test.dart @@ -0,0 +1,132 @@ +// Copyright 2013 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. + +import 'dart:async'; +import 'dart:convert'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:google_identity_services_web/oauth2.dart'; +import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart'; +import 'package:google_sign_in_web/src/people.dart'; +import 'package:http/http.dart' as http; +import 'package:http/testing.dart' as http_test; +import 'package:integration_test/integration_test.dart'; + +import 'src/jsify_as.dart'; +import 'src/person.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + group('requestUserData', () { + const String expectedAccessToken = '3xp3c73d_4cc355_70k3n'; + + final TokenResponse fakeToken = jsifyAs({ + 'token_type': 'Bearer', + 'access_token': expectedAccessToken, + }); + + testWidgets('happy case', (_) async { + final Completer accessTokenCompleter = Completer(); + + final http.Client mockClient = http_test.MockClient( + (http.Request request) async { + accessTokenCompleter.complete(request.headers['Authorization']); + + return http.Response( + jsonEncode(person), + 200, + headers: {'content-type': 'application/json'}, + ); + }, + ); + + final GoogleSignInUserData? user = await requestUserData( + fakeToken, + overrideClient: mockClient, + ); + + expect(user, isNotNull); + expect(user!.email, expectedPersonEmail); + expect(user.id, expectedPersonId); + expect(user.displayName, expectedPersonName); + expect(user.photoUrl, expectedPersonPhoto); + expect(user.idToken, isNull); + expect( + accessTokenCompleter.future, + completion('Bearer $expectedAccessToken'), + ); + }); + + testWidgets('Unauthorized request - throws exception', (_) async { + final http.Client mockClient = http_test.MockClient( + (http.Request request) async { + return http.Response( + 'Unauthorized', + 403, + ); + }, + ); + + expect(() async { + await requestUserData( + fakeToken, + overrideClient: mockClient, + ); + }, throwsA(isA())); + }); + }); + + group('extractUserData', () { + testWidgets('happy case', (_) async { + final GoogleSignInUserData? user = extractUserData(person); + + expect(user, isNotNull); + expect(user!.email, expectedPersonEmail); + expect(user.id, expectedPersonId); + expect(user.displayName, expectedPersonName); + expect(user.photoUrl, expectedPersonPhoto); + expect(user.idToken, isNull); + }); + + testWidgets('no name/photo - keeps going', (_) async { + final Map personWithoutSomeData = + mapWithoutKeys(person, { + 'names', + 'photos', + }); + + final GoogleSignInUserData? user = extractUserData(personWithoutSomeData); + + expect(user, isNotNull); + expect(user!.email, expectedPersonEmail); + expect(user.id, expectedPersonId); + expect(user.displayName, isNull); + expect(user.photoUrl, isNull); + expect(user.idToken, isNull); + }); + + testWidgets('no userId - throws assertion error', (_) async { + final Map personWithoutId = + mapWithoutKeys(person, { + 'resourceName', + }); + + expect(() { + extractUserData(personWithoutId); + }, throwsAssertionError); + }); + + testWidgets('no email - throws assertion error', (_) async { + final Map personWithoutEmail = + mapWithoutKeys(person, { + 'emailAddresses', + }); + + expect(() { + extractUserData(personWithoutEmail); + }, throwsAssertionError); + }); + }); +} diff --git a/packages/google_sign_in/google_sign_in_web/example/integration_test/src/dom.dart b/packages/google_sign_in/google_sign_in_web/example/integration_test/src/dom.dart new file mode 100644 index 000000000000..f7d3152a7e64 --- /dev/null +++ b/packages/google_sign_in/google_sign_in_web/example/integration_test/src/dom.dart @@ -0,0 +1,59 @@ +// Copyright 2013 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. + +/* +// DOM shim. This file contains everything we need from the DOM API written as +// @staticInterop, so we don't need dart:html +// https://developer.mozilla.org/en-US/docs/Web/API/ +// +// (To be replaced by `package:web`) +*/ + +import 'package:js/js.dart'; + +/// Document interface +@JS() +@staticInterop +abstract class DomHtmlDocument {} + +/// Some methods of document +extension DomHtmlDocumentExtension on DomHtmlDocument { + /// document.head + external DomHtmlElement get head; + + /// document.createElement + external DomHtmlElement createElement(String tagName); +} + +/// An instance of an HTMLElement +@JS() +@staticInterop +abstract class DomHtmlElement {} + +/// (Some) methods of HtmlElement +extension DomHtmlElementExtension on DomHtmlElement { + /// Node.appendChild + external DomHtmlElement appendChild(DomHtmlElement child); + + /// Element.remove + external void remove(); +} + +/// An instance of an HTMLMetaElement +@JS() +@staticInterop +abstract class DomHtmlMetaElement extends DomHtmlElement {} + +/// Some methods exclusive of Script elements +extension DomHtmlMetaElementExtension on DomHtmlMetaElement { + external set name(String name); + external set content(String content); +} + +// Getters + +/// window.document +@JS() +@staticInterop +external DomHtmlDocument get document; diff --git a/packages/google_sign_in/google_sign_in_web/example/integration_test/src/jsify_as.dart b/packages/google_sign_in/google_sign_in_web/example/integration_test/src/jsify_as.dart new file mode 100644 index 000000000000..82547b284fe0 --- /dev/null +++ b/packages/google_sign_in/google_sign_in_web/example/integration_test/src/jsify_as.dart @@ -0,0 +1,10 @@ +// Copyright 2013 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. + +import 'package:js/js_util.dart' as js_util; + +/// Converts a [data] object into a JS Object of type `T`. +T jsifyAs(Map data) { + return js_util.jsify(data) as T; +} diff --git a/packages/google_sign_in/google_sign_in_web/example/integration_test/src/jwt_examples.dart b/packages/google_sign_in/google_sign_in_web/example/integration_test/src/jwt_examples.dart new file mode 100644 index 000000000000..72841c5165ee --- /dev/null +++ b/packages/google_sign_in/google_sign_in_web/example/integration_test/src/jwt_examples.dart @@ -0,0 +1,46 @@ +// Copyright 2013 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. + +import 'package:google_identity_services_web/id.dart'; + +import 'jsify_as.dart'; + +/// A CredentialResponse with null `credential`. +final CredentialResponse nullCredential = + jsifyAs({ + 'credential': null, +}); + +/// A CredentialResponse wrapping a known good JWT Token as its `credential`. +final CredentialResponse goodCredential = + jsifyAs({ + 'credential': goodJwtToken, +}); + +/// A JWT token with predefined values. +/// +/// 'email': 'adultman@example.com', +/// 'sub': '123456', +/// 'name': 'Vincent Adultman', +/// 'picture': 'https://thispersondoesnotexist.com/image?x=.jpg', +/// +/// Signed with HS256 and the private key: 'symmetric-encryption-is-weak' +const String goodJwtToken = + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.$goodPayload.lqzULA_U3YzEl_-fL7YLU-kFXmdD2ttJLTv-UslaNQ4'; + +/// The payload of a JWT token that contains predefined values. +/// +/// 'email': 'adultman@example.com', +/// 'sub': '123456', +/// 'name': 'Vincent Adultman', +/// 'picture': 'https://thispersondoesnotexist.com/image?x=.jpg', +const String goodPayload = + 'eyJlbWFpbCI6ImFkdWx0bWFuQGV4YW1wbGUuY29tIiwic3ViIjoiMTIzNDU2IiwibmFtZSI6IlZpbmNlbnQgQWR1bHRtYW4iLCJwaWN0dXJlIjoiaHR0cHM6Ly90aGlzcGVyc29uZG9lc25vdGV4aXN0LmNvbS9pbWFnZT94PS5qcGcifQ'; + +// More encrypted JWT Tokens may be created on https://jwt.io. +// +// First, decode the `goodJwtToken` above, modify to your heart's +// content, and add a new credential here. +// +// (New tokens can also be created with `package:jose` and `dart:convert`.) diff --git a/packages/google_sign_in/google_sign_in_web/example/integration_test/src/person.dart b/packages/google_sign_in/google_sign_in_web/example/integration_test/src/person.dart new file mode 100644 index 000000000000..2525596eabe9 --- /dev/null +++ b/packages/google_sign_in/google_sign_in_web/example/integration_test/src/person.dart @@ -0,0 +1,66 @@ +// Copyright 2013 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. + +const String expectedPersonId = '1234567890'; +const String expectedPersonName = 'Vincent Adultman'; +const String expectedPersonEmail = 'adultman@example.com'; +const String expectedPersonPhoto = + 'https://thispersondoesnotexist.com/image?x=.jpg'; + +/// A subset of https://developers.google.com/people/api/rest/v1/people#Person. +final Map person = { + 'resourceName': 'people/$expectedPersonId', + 'emailAddresses': [ + { + 'metadata': { + 'primary': false, + }, + 'value': 'bad@example.com', + }, + { + 'metadata': {}, + 'value': 'nope@example.com', + }, + { + 'metadata': { + 'primary': true, + }, + 'value': expectedPersonEmail, + }, + ], + 'names': [ + { + 'metadata': { + 'primary': true, + }, + 'displayName': expectedPersonName, + }, + { + 'metadata': { + 'primary': false, + }, + 'displayName': 'Fakey McFakeface', + }, + ], + 'photos': [ + { + 'metadata': { + 'primary': true, + }, + 'url': expectedPersonPhoto, + }, + ], +}; + +/// Returns a copy of [map] without the [keysToRemove]. +T mapWithoutKeys>( + T map, + Set keysToRemove, +) { + return Map.fromEntries( + map.entries.where((MapEntry entry) { + return !keysToRemove.contains(entry.key); + }), + ) as T; +} diff --git a/packages/google_sign_in/google_sign_in_web/example/integration_test/src/test_utils.dart b/packages/google_sign_in/google_sign_in_web/example/integration_test/src/test_utils.dart deleted file mode 100644 index 56aa61df136e..000000000000 --- a/packages/google_sign_in/google_sign_in_web/example/integration_test/src/test_utils.dart +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2013 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. - -import 'dart:convert'; - -String toBase64Url(String contents) { - // Open the file - return 'data:text/javascript;base64,${base64.encode(utf8.encode(contents))}'; -} diff --git a/packages/google_sign_in/google_sign_in_web/example/integration_test/utils_test.dart b/packages/google_sign_in/google_sign_in_web/example/integration_test/utils_test.dart new file mode 100644 index 000000000000..82701e587be1 --- /dev/null +++ b/packages/google_sign_in/google_sign_in_web/example/integration_test/utils_test.dart @@ -0,0 +1,173 @@ +// Copyright 2013 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. + +import 'dart:convert'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:google_identity_services_web/id.dart'; +import 'package:google_identity_services_web/oauth2.dart'; +import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart'; +import 'package:google_sign_in_web/src/utils.dart'; +import 'package:integration_test/integration_test.dart'; + +import 'src/jsify_as.dart'; +import 'src/jwt_examples.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + group('gisResponsesToTokenData', () { + testWidgets('null objects -> no problem', (_) async { + final GoogleSignInTokenData tokens = gisResponsesToTokenData(null, null); + + expect(tokens.accessToken, isNull); + expect(tokens.idToken, isNull); + expect(tokens.serverAuthCode, isNull); + }); + + testWidgets('non-null objects are correctly used', (_) async { + const String expectedIdToken = 'some-value-for-testing'; + const String expectedAccessToken = 'another-value-for-testing'; + + final CredentialResponse credential = + jsifyAs({ + 'credential': expectedIdToken, + }); + final TokenResponse token = jsifyAs({ + 'access_token': expectedAccessToken, + }); + final GoogleSignInTokenData tokens = + gisResponsesToTokenData(credential, token); + + expect(tokens.accessToken, expectedAccessToken); + expect(tokens.idToken, expectedIdToken); + expect(tokens.serverAuthCode, isNull); + }); + }); + + group('gisResponsesToUserData', () { + testWidgets('happy case', (_) async { + final GoogleSignInUserData data = gisResponsesToUserData(goodCredential)!; + + expect(data.displayName, 'Vincent Adultman'); + expect(data.id, '123456'); + expect(data.email, 'adultman@example.com'); + expect(data.photoUrl, 'https://thispersondoesnotexist.com/image?x=.jpg'); + expect(data.idToken, goodJwtToken); + }); + + testWidgets('null response -> null', (_) async { + expect(gisResponsesToUserData(null), isNull); + }); + + testWidgets('null response.credential -> null', (_) async { + expect(gisResponsesToUserData(nullCredential), isNull); + }); + + testWidgets('invalid payload -> null', (_) async { + final CredentialResponse response = + jsifyAs({ + 'credential': 'some-bogus.thing-that-is-not.valid-jwt', + }); + expect(gisResponsesToUserData(response), isNull); + }); + }); + + group('getJwtTokenPayload', () { + testWidgets('happy case -> data', (_) async { + final Map? data = getJwtTokenPayload(goodJwtToken); + + expect(data, isNotNull); + expect(data, containsPair('name', 'Vincent Adultman')); + expect(data, containsPair('email', 'adultman@example.com')); + expect(data, containsPair('sub', '123456')); + expect( + data, + containsPair( + 'picture', + 'https://thispersondoesnotexist.com/image?x=.jpg', + )); + }); + + testWidgets('null Token -> null', (_) async { + final Map? data = getJwtTokenPayload(null); + + expect(data, isNull); + }); + + testWidgets('Token not matching the format -> null', (_) async { + final Map? data = getJwtTokenPayload('1234.4321'); + + expect(data, isNull); + }); + + testWidgets('Bad token that matches the format -> null', (_) async { + final Map? data = getJwtTokenPayload('1234.abcd.4321'); + + expect(data, isNull); + }); + }); + + group('decodeJwtPayload', () { + testWidgets('Good payload -> data', (_) async { + final Map? data = decodeJwtPayload(goodPayload); + + expect(data, isNotNull); + expect(data, containsPair('name', 'Vincent Adultman')); + expect(data, containsPair('email', 'adultman@example.com')); + expect(data, containsPair('sub', '123456')); + expect( + data, + containsPair( + 'picture', + 'https://thispersondoesnotexist.com/image?x=.jpg', + )); + }); + + testWidgets('Proper JSON payload -> data', (_) async { + final String payload = base64.encode(utf8.encode('{"properJson": true}')); + + final Map? data = decodeJwtPayload(payload); + + expect(data, isNotNull); + expect(data, containsPair('properJson', true)); + }); + + testWidgets('Not-normalized base-64 payload -> data', (_) async { + // This is the payload generated by the "Proper JSON payload" test, but + // we remove the leading "=" symbols so it's length is not a multiple of 4 + // anymore! + final String payload = 'eyJwcm9wZXJKc29uIjogdHJ1ZX0='.replaceAll('=', ''); + + final Map? data = decodeJwtPayload(payload); + + expect(data, isNotNull); + expect(data, containsPair('properJson', true)); + }); + + testWidgets('Invalid JSON payload -> null', (_) async { + final String payload = base64.encode(utf8.encode('{properJson: false}')); + + final Map? data = decodeJwtPayload(payload); + + expect(data, isNull); + }); + + testWidgets('Non JSON payload -> null', (_) async { + final String payload = base64.encode(utf8.encode('not-json')); + + final Map? data = decodeJwtPayload(payload); + + expect(data, isNull); + }); + + testWidgets('Non base-64 payload -> null', (_) async { + const String payload = 'not-base-64-at-all'; + + final Map? data = decodeJwtPayload(payload); + + expect(data, isNull); + }); + }); +} diff --git a/packages/google_sign_in/google_sign_in_web/example/pubspec.yaml b/packages/google_sign_in/google_sign_in_web/example/pubspec.yaml index 848517534ed2..c73953374696 100644 --- a/packages/google_sign_in/google_sign_in_web/example/pubspec.yaml +++ b/packages/google_sign_in/google_sign_in_web/example/pubspec.yaml @@ -12,12 +12,15 @@ dependencies: path: ../ dev_dependencies: + build_runner: ^2.1.1 flutter_driver: sdk: flutter flutter_test: sdk: flutter + google_identity_services_web: ^0.2.0 google_sign_in_platform_interface: ^2.2.0 http: ^0.13.0 integration_test: sdk: flutter js: ^0.6.3 + mockito: ^5.3.2 diff --git a/packages/google_sign_in/google_sign_in_web/example/regen_mocks.sh b/packages/google_sign_in/google_sign_in_web/example/regen_mocks.sh new file mode 100755 index 000000000000..78bcdc0f9e28 --- /dev/null +++ b/packages/google_sign_in/google_sign_in_web/example/regen_mocks.sh @@ -0,0 +1,10 @@ +#!/usr/bin/bash +# Copyright 2013 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. + +flutter pub get + +echo "(Re)generating mocks." + +flutter pub run build_runner build --delete-conflicting-outputs diff --git a/packages/google_sign_in/google_sign_in_web/example/run_test.sh b/packages/google_sign_in/google_sign_in_web/example/run_test.sh index 28877dce8d6e..fcac5f600acb 100755 --- a/packages/google_sign_in/google_sign_in_web/example/run_test.sh +++ b/packages/google_sign_in/google_sign_in_web/example/run_test.sh @@ -6,9 +6,11 @@ if pgrep -lf chromedriver > /dev/null; then echo "chromedriver is running." + ./regen_mocks.sh + if [ $# -eq 0 ]; then echo "No target specified, running all tests..." - find integration_test/ -iname *_test.dart | xargs -n1 -i -t flutter drive -d web-server --web-port=7357 --browser-name=chrome --driver=test_driver/integration_test.dart --target='{}' + find integration_test/ -iname *_test.dart | xargs -n1 -i -t flutter drive -d web-server --web-port=7357 --browser-name=chrome --driver=test_driver/integration_test.dart --target='{}' else echo "Running test target: $1..." set -x @@ -17,7 +19,6 @@ if pgrep -lf chromedriver > /dev/null; then else echo "chromedriver is not running." + echo "Please, check the README.md for instructions on how to use run_test.sh" fi - - 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 index 5d75c0da0c4f..827b17ca5b44 100644 --- 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 @@ -5,23 +5,22 @@ import 'dart:async'; import 'dart:html' as html; -import 'package:flutter/foundation.dart' show visibleForTesting; -import 'package:flutter/services.dart'; +import 'package:flutter/foundation.dart' show visibleForTesting, kDebugMode; +import 'package:flutter/services.dart' show PlatformException; import 'package:flutter_web_plugins/flutter_web_plugins.dart'; +import 'package:google_identity_services_web/loader.dart' as loader; import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart'; -import 'package:js/js.dart'; -import 'src/js_interop/gapiauth2.dart' as auth2; -import 'src/load_gapi.dart' as gapi; -import 'src/utils.dart' show gapiUserToPluginUserData; +import 'src/gis_client.dart'; -const String _kClientIdMetaSelector = 'meta[name=google-signin-client_id]'; -const String _kClientIdAttributeName = 'content'; +/// The `name` of the meta-tag to define a ClientID in HTML. +const String clientIdMetaName = 'google-signin-client_id'; -/// This is only exposed for testing. It shouldn't be accessed by users of the -/// plugin as it could break at any point. -@visibleForTesting -String gapiUrl = 'https://apis.google.com/js/platform.js'; +/// The selector used to find the meta-tag that defines a ClientID in HTML. +const String clientIdMetaSelector = 'meta[name=$clientIdMetaName]'; + +/// The attribute name that stores the Client ID in the meta-tag that defines a Client ID in HTML. +const String clientIdAttributeName = 'content'; /// Implementation of the google_sign_in plugin for Web. class GoogleSignInPlugin extends GoogleSignInPlatform { @@ -29,18 +28,24 @@ class GoogleSignInPlugin extends GoogleSignInPlatform { /// background. /// /// The plugin is completely initialized when [initialized] completed. - GoogleSignInPlugin() { - _autoDetectedClientId = html - .querySelector(_kClientIdMetaSelector) - ?.getAttribute(_kClientIdAttributeName); - - _isGapiInitialized = gapi.inject(gapiUrl).then((_) => gapi.init()); + GoogleSignInPlugin({@visibleForTesting bool debugOverrideLoader = false}) { + autoDetectedClientId = html + .querySelector(clientIdMetaSelector) + ?.getAttribute(clientIdAttributeName); + + if (debugOverrideLoader) { + _jsSdkLoadedFuture = Future.value(true); + } else { + _jsSdkLoadedFuture = loader.loadWebSdk(); + } } - late Future _isGapiInitialized; - late Future _isAuthInitialized; + late Future _jsSdkLoadedFuture; bool _isInitCalled = false; + // The instance of [GisSdkClient] backing the plugin. + late GisSdkClient _gisClient; + // This method throws if init or initWithParams hasn't been called at some // point in the past. It is used by the [initialized] getter to ensure that // users can't await on a Future that will never resolve. @@ -53,14 +58,16 @@ class GoogleSignInPlugin extends GoogleSignInPlatform { } } - /// A future that resolves when both GAPI and Auth2 have been correctly initialized. + /// A future that resolves when the SDK has been correctly loaded. @visibleForTesting Future get initialized { _assertIsInitCalled(); - return Future.wait(>[_isGapiInitialized, _isAuthInitialized]); + return _jsSdkLoadedFuture; } - String? _autoDetectedClientId; + /// Stores the client ID if it was set in a meta-tag of the page. + @visibleForTesting + late String? autoDetectedClientId; /// Factory method that initializes the plugin with [GoogleSignInPlatform]. static void registerWith(Registrar registrar) { @@ -83,8 +90,11 @@ class GoogleSignInPlugin extends GoogleSignInPlatform { } @override - Future initWithParams(SignInInitParameters params) async { - final String? appClientId = params.clientId ?? _autoDetectedClientId; + Future initWithParams( + SignInInitParameters params, { + @visibleForTesting GisSdkClient? overrideClient, + }) async { + final String? appClientId = params.clientId ?? autoDetectedClientId; assert( appClientId != null, 'ClientID not set. Either set it on a ' @@ -100,141 +110,95 @@ class GoogleSignInPlugin extends GoogleSignInPlatform { 'Check https://developers.google.com/identity/protocols/googlescopes ' 'for a list of valid OAuth 2.0 scopes.'); - await _isGapiInitialized; + await _jsSdkLoadedFuture; - final auth2.GoogleAuth auth = auth2.init(auth2.ClientConfig( - hosted_domain: params.hostedDomain, - // The js lib wants a space-separated list of values - scope: params.scopes.join(' '), - client_id: appClientId!, - plugin_name: 'dart-google_sign_in_web', - )); + _gisClient = overrideClient ?? + GisSdkClient( + clientId: appClientId!, + hostedDomain: params.hostedDomain, + initialScopes: List.from(params.scopes), + loggingEnabled: kDebugMode, + ); - final Completer isAuthInitialized = Completer(); - _isAuthInitialized = isAuthInitialized.future; _isInitCalled = true; - - auth.then(allowInterop((auth2.GoogleAuth initializedAuth) { - // onSuccess - - // TODO(ditman): https://github.com/flutter/flutter/issues/48528 - // This plugin doesn't notify the app of external changes to the - // state of the authentication, i.e: if you logout elsewhere... - - isAuthInitialized.complete(); - }), allowInterop((auth2.GoogleAuthInitFailureError reason) { - // onError - isAuthInitialized.completeError(PlatformException( - code: reason.error, - message: reason.details, - details: - 'https://developers.google.com/identity/sign-in/web/reference#error_codes', - )); - })); - - return _isAuthInitialized; } @override Future signInSilently() async { await initialized; - return gapiUserToPluginUserData( - auth2.getAuthInstance()?.currentUser?.get()); + // Since the new GIS SDK does *not* perform authorization at the same time as + // authentication (and every one of our users expects that), we need to tell + // the plugin that this failed regardless of the actual result. + // + // However, if this succeeds, we'll save a People API request later. + return _gisClient.signInSilently().then((_) => null); } @override Future signIn() async { await initialized; + + // This method mainly does oauth2 authorization, which happens to also do + // authentication if needed. However, the authentication information is not + // returned anymore. + // + // This method will synthesize authentication information from the People API + // if needed (or use the last identity seen from signInSilently). try { - return gapiUserToPluginUserData(await auth2.getAuthInstance()?.signIn()); - } on auth2.GoogleAuthSignInError catch (reason) { + return _gisClient.signIn(); + } catch (reason) { throw PlatformException( - code: reason.error, - message: 'Exception raised from GoogleAuth.signIn()', + code: reason.toString(), + message: 'Exception raised from signIn', details: - 'https://developers.google.com/identity/sign-in/web/reference#error_codes_2', + 'https://developers.google.com/identity/oauth2/web/guides/error', ); } } @override - Future getTokens( - {required String email, bool? shouldRecoverAuth}) async { + 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); + return _gisClient.getTokens(); } @override Future signOut() async { await initialized; - return auth2.getAuthInstance()?.signOut(); + _gisClient.signOut(); } @override Future disconnect() async { await initialized; - final auth2.GoogleUser? currentUser = - auth2.getAuthInstance()?.currentUser?.get(); - - if (currentUser == null) { - return; - } - - return currentUser.disconnect(); + _gisClient.disconnect(); } @override Future isSignedIn() async { await initialized; - final auth2.GoogleUser? currentUser = - auth2.getAuthInstance()?.currentUser?.get(); - - if (currentUser == null) { - return false; - } - - return currentUser.isSignedIn(); + return _gisClient.isSignedIn(); } @override Future clearAuthCache({required String token}) async { await initialized; - return auth2.getAuthInstance()?.disconnect(); + _gisClient.clearAuthCache(); } @override Future requestScopes(List scopes) async { await initialized; - final auth2.GoogleUser? currentUser = - auth2.getAuthInstance()?.currentUser?.get(); - - if (currentUser == null) { - return false; - } - - final String grantedScopes = currentUser.getGrantedScopes() ?? ''; - final Iterable missingScopes = - scopes.where((String scope) => !grantedScopes.contains(scope)); - - if (missingScopes.isEmpty) { - return true; - } - - final Object? response = await currentUser - .grant(auth2.SigninOptions(scope: missingScopes.join(' '))); - - return response != null; + return _gisClient.requestScopes(scopes); } } diff --git a/packages/google_sign_in/google_sign_in_web/lib/src/gis_client.dart b/packages/google_sign_in/google_sign_in_web/lib/src/gis_client.dart new file mode 100644 index 000000000000..3815322e6900 --- /dev/null +++ b/packages/google_sign_in/google_sign_in_web/lib/src/gis_client.dart @@ -0,0 +1,310 @@ +// Copyright 2013 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. +import 'dart:async'; + +// TODO(dit): Split `id` and `oauth2` "services" for mocking. https://github.com/flutter/flutter/issues/120657 +import 'package:google_identity_services_web/id.dart'; +import 'package:google_identity_services_web/oauth2.dart'; +import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart'; +// ignore: unnecessary_import +import 'package:js/js.dart'; +import 'package:js/js_util.dart'; + +import 'people.dart' as people; +import 'utils.dart' as utils; + +/// A client to hide (most) of the interaction with the GIS SDK from the plugin. +/// +/// (Overridable for testing) +class GisSdkClient { + /// Create a GisSdkClient object. + GisSdkClient({ + required List initialScopes, + required String clientId, + bool loggingEnabled = false, + String? hostedDomain, + }) : _initialScopes = initialScopes { + if (loggingEnabled) { + id.setLogLevel('debug'); + } + // Configure the Stream objects that are going to be used by the clients. + _configureStreams(); + + // Initialize the SDK clients we need. + _initializeIdClient( + clientId, + onResponse: _onCredentialResponse, + ); + + _tokenClient = _initializeTokenClient( + clientId, + hostedDomain: hostedDomain, + onResponse: _onTokenResponse, + onError: _onTokenError, + ); + } + + // Configure the credential (authentication) and token (authorization) response streams. + void _configureStreams() { + _tokenResponses = StreamController.broadcast(); + _credentialResponses = StreamController.broadcast(); + _tokenResponses.stream.listen((TokenResponse response) { + _lastTokenResponse = response; + }, onError: (Object error) { + _lastTokenResponse = null; + }); + _credentialResponses.stream.listen((CredentialResponse response) { + _lastCredentialResponse = response; + }, onError: (Object error) { + _lastCredentialResponse = null; + }); + } + + // Initializes the `id` SDK for the silent-sign in (authentication) client. + void _initializeIdClient( + String clientId, { + required CallbackFn onResponse, + }) { + // Initialize `id` for the silent-sign in code. + final IdConfiguration idConfig = IdConfiguration( + client_id: clientId, + callback: allowInterop(onResponse), + cancel_on_tap_outside: false, + auto_select: true, // Attempt to sign-in silently. + ); + id.initialize(idConfig); + } + + // Handle a "normal" credential (authentication) response. + // + // (Normal doesn't mean successful, this might contain `error` information.) + void _onCredentialResponse(CredentialResponse response) { + if (response.error != null) { + _credentialResponses.addError(response.error!); + } else { + _credentialResponses.add(response); + } + } + + // Creates a `oauth2.TokenClient` used for authorization (scope) requests. + TokenClient _initializeTokenClient( + String clientId, { + String? hostedDomain, + required TokenClientCallbackFn onResponse, + required ErrorCallbackFn onError, + }) { + // Create a Token Client for authorization calls. + final TokenClientConfig tokenConfig = TokenClientConfig( + client_id: clientId, + hosted_domain: hostedDomain, + callback: allowInterop(_onTokenResponse), + error_callback: allowInterop(_onTokenError), + // `scope` will be modified by the `signIn` method, in case we need to + // backfill user Profile info. + scope: ' ', + ); + return oauth2.initTokenClient(tokenConfig); + } + + // Handle a "normal" token (authorization) response. + // + // (Normal doesn't mean successful, this might contain `error` information.) + void _onTokenResponse(TokenResponse response) { + if (response.error != null) { + _tokenResponses.addError(response.error!); + } else { + _tokenResponses.add(response); + } + } + + // Handle a "not-directly-related-to-authorization" error. + // + // Token clients have an additional `error_callback` for miscellaneous + // errors, like "popup couldn't open" or "popup closed by user". + void _onTokenError(Object? error) { + // This is handled in a funky (js_interop) way because of: + // https://github.com/dart-lang/sdk/issues/50899 + _tokenResponses.addError(getProperty(error!, 'type')); + } + + /// Attempts to sign-in the user using the OneTap UX flow. + /// + /// If the user consents, to OneTap, the [GoogleSignInUserData] will be + /// generated from a proper [CredentialResponse], which contains `idToken`. + /// Else, it'll be synthesized by a request to the People API later, and the + /// `idToken` will be null. + Future signInSilently() async { + final Completer userDataCompleter = + Completer(); + + // Ask the SDK to render the OneClick sign-in. + // + // And also handle its "moments". + id.prompt(allowInterop((PromptMomentNotification moment) { + _onPromptMoment(moment, userDataCompleter); + })); + + return userDataCompleter.future; + } + + // Handles "prompt moments" of the OneClick card UI. + // + // See: https://developers.google.com/identity/gsi/web/guides/receive-notifications-prompt-ui-status + Future _onPromptMoment( + PromptMomentNotification moment, + Completer completer, + ) async { + if (completer.isCompleted) { + return; // Skip once the moment has been handled. + } + + if (moment.isDismissedMoment() && + moment.getDismissedReason() == + MomentDismissedReason.credential_returned) { + // Kick this part of the handler to the bottom of the JS event queue, so + // the _credentialResponses stream has time to propagate its last value, + // and we can use _lastCredentialResponse. + return Future.delayed(Duration.zero, () { + completer + .complete(utils.gisResponsesToUserData(_lastCredentialResponse)); + }); + } + + // In any other 'failed' moments, return null and add an error to the stream. + if (moment.isNotDisplayed() || + moment.isSkippedMoment() || + moment.isDismissedMoment()) { + final String reason = moment.getNotDisplayedReason()?.toString() ?? + moment.getSkippedReason()?.toString() ?? + moment.getDismissedReason()?.toString() ?? + 'unknown_error'; + + _credentialResponses.addError(reason); + completer.complete(null); + } + } + + /// Starts an oauth2 "implicit" flow to authorize requests. + /// + /// The new GIS SDK does not return user authentication from this flow, so: + /// * If [_lastCredentialResponse] is **not** null (the user has successfully + /// `signInSilently`), we return that after this method completes. + /// * If [_lastCredentialResponse] is null, we add [people.scopes] to the + /// [_initialScopes], so we can retrieve User Profile information back + /// from the People API (without idToken). See [people.requestUserData]. + Future signIn() async { + // If we already know the user, use their `email` as a `hint`, so they don't + // have to pick their user again in the Authorization popup. + final GoogleSignInUserData? knownUser = + utils.gisResponsesToUserData(_lastCredentialResponse); + // This toggles a popup, so `signIn` *must* be called with + // user activation. + _tokenClient.requestAccessToken(OverridableTokenClientConfig( + prompt: knownUser == null ? 'select_account' : '', + hint: knownUser?.email, + scope: [ + ..._initialScopes, + // If the user hasn't gone through the auth process, + // the plugin will attempt to `requestUserData` after, + // so we need extra scopes to retrieve that info. + if (_lastCredentialResponse == null) ...people.scopes, + ].join(' '), + )); + + await _tokenResponses.stream.first; + + return _computeUserDataForLastToken(); + } + + // This function returns the currently signed-in [GoogleSignInUserData]. + // + // It'll do a request to the People API (if needed). + Future _computeUserDataForLastToken() async { + // If the user hasn't authenticated, request their basic profile info + // from the People API. + // + // This synthetic response will *not* contain an `idToken` field. + if (_lastCredentialResponse == null && _requestedUserData == null) { + assert(_lastTokenResponse != null); + _requestedUserData = await people.requestUserData(_lastTokenResponse!); + } + // Complete user data either with the _lastCredentialResponse seen, + // or the synthetic _requestedUserData from above. + return utils.gisResponsesToUserData(_lastCredentialResponse) ?? + _requestedUserData; + } + + /// Returns a [GoogleSignInTokenData] from the latest seen responses. + GoogleSignInTokenData getTokens() { + return utils.gisResponsesToTokenData( + _lastCredentialResponse, + _lastTokenResponse, + ); + } + + /// Revokes the current authentication. + Future signOut() async { + clearAuthCache(); + id.disableAutoSelect(); + } + + /// Revokes the current authorization and authentication. + Future disconnect() async { + if (_lastTokenResponse != null) { + oauth2.revoke(_lastTokenResponse!.access_token); + } + signOut(); + } + + /// Returns true if the client has recognized this user before. + Future isSignedIn() async { + return _lastCredentialResponse != null || _requestedUserData != null; + } + + /// Clears all the cached results from authentication and authorization. + Future clearAuthCache() async { + _lastCredentialResponse = null; + _lastTokenResponse = null; + _requestedUserData = null; + } + + /// Requests the list of [scopes] passed in to the client. + /// + /// Keeps the previously granted scopes. + Future requestScopes(List scopes) async { + _tokenClient.requestAccessToken(OverridableTokenClientConfig( + scope: scopes.join(' '), + include_granted_scopes: true, + )); + + await _tokenResponses.stream.first; + + return oauth2.hasGrantedAllScopes(_lastTokenResponse!, scopes); + } + + // The scopes initially requested by the developer. + // + // We store this because we might need to add more at `signIn`. If the user + // doesn't `silentSignIn`, we expand this list to consult the People API to + // return some basic Authentication information. + final List _initialScopes; + + // The Google Identity Services client for oauth requests. + late TokenClient _tokenClient; + + // Streams of credential and token responses. + late StreamController _credentialResponses; + late StreamController _tokenResponses; + + // The last-seen credential and token responses + CredentialResponse? _lastCredentialResponse; + TokenResponse? _lastTokenResponse; + + // If the user *authenticates* (signs in) through oauth2, the SDK doesn't return + // identity information anymore, so we synthesize it by calling the PeopleAPI + // (if needed) + // + // (This is a synthetic _lastCredentialResponse) + GoogleSignInUserData? _requestedUserData; +} diff --git a/packages/google_sign_in/google_sign_in_web/lib/src/js_interop/gapi.dart b/packages/google_sign_in/google_sign_in_web/lib/src/js_interop/gapi.dart deleted file mode 100644 index 3be4b2d77b66..000000000000 --- a/packages/google_sign_in/google_sign_in_web/lib/src/js_interop/gapi.dart +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2013 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. - -/// 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 - -// https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/gapi - -// ignore_for_file: public_member_api_docs, -// * public_member_api_docs originally undocumented because the file was -// autogenerated. - -@JS() -library gapi; - -import 'package:js/js.dart'; - -// Module gapi -typedef LoadCallback = void Function( - [dynamic args1, - dynamic args2, - dynamic args3, - dynamic args4, - dynamic args5]); - -@anonymous -@JS() -abstract class LoadConfig { - external factory LoadConfig( - {LoadCallback callback, - Function? onerror, - num? timeout, - Function? ontimeout}); - 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); -} - -/*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 - -// Manually removed gapi.auth and gapi.client, unused by this plugin. diff --git a/packages/google_sign_in/google_sign_in_web/lib/src/js_interop/gapiauth2.dart b/packages/google_sign_in/google_sign_in_web/lib/src/js_interop/gapiauth2.dart deleted file mode 100644 index 35a2d08e74b6..000000000000 --- a/packages/google_sign_in/google_sign_in_web/lib/src/js_interop/gapiauth2.dart +++ /dev/null @@ -1,497 +0,0 @@ -// Copyright 2013 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. - -/// 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 - -/// - -// https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/gapi.auth2 - -// ignore_for_file: public_member_api_docs, non_constant_identifier_names, -// * public_member_api_docs originally undocumented because the file was -// autogenerated. -// * non_constant_identifier_names required to be able to use the same parameter -// names as the underlying library. - -@JS() -library gapiauth2; - -import 'package:js/js.dart'; -import 'package:js/js_util.dart' show promiseToFuture; - -@anonymous -@JS() -class GoogleAuthInitFailureError { - external String get error; - external set error(String? value); - - external String get details; - external set details(String? value); -} - -@anonymous -@JS() -class GoogleAuthSignInError { - external String get error; - external set error(String value); -} - -@anonymous -@JS() -class OfflineAccessResponse { - external String? get code; - external set code(String? value); -} - -// 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 { - 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 Function(GoogleAuth googleAuth) onInit, - [dynamic Function(GoogleAuthInitFailureError reason) onFailure]); - - /// 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 Function(GoogleUser googleUser) onsuccess, - dynamic Function(String reason) onfailure); -} - -@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 _GoogleAuth tt = this as _GoogleAuth; - return promiseToFuture(tt.signIn(options)); - } - - Future grantOfflineAccess( - [OfflineAccessOptions? options]) { - final _GoogleAuth tt = this as _GoogleAuth; - 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 Function(bool signedIn) listener); -} - -@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 Function(GoogleUser user) listener); -} - -@anonymous -@JS() -abstract class SigninOptions { - external factory SigninOptions( - {String app_package_name, - bool fetch_basic_profile, - String prompt, - String scope, - String /*'popup'|'redirect'*/ ux_mode, - String redirect_uri, - String login_hint}); - - /// 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); - - // When your app knows which user it is trying to authenticate, it can provide this parameter as a hint to the authentication server. - // Passing this hint suppresses the account chooser and either pre-fill the email box on the sign-in form, or select the proper session (if the user is using multiple sign-in), - // which can help you avoid problems that occur if your app logs in the wrong user account. The value can be either an email address or the sub string, - // which is equivalent to the user's Google ID. - // https://developers.google.com/identity/protocols/OpenIDConnect?hl=en#authenticationuriparameters - external String? get login_hint; - external set login_hint(String? v); -} - -/// 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 factory OfflineAccessOptions( - {String scope, - String /*'select_account'|'consent'*/ prompt, - String app_package_name}); - 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); -} - -/// 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 { - 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, - String plugin_name, - }); - - /// 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); - - /// Allows newly created Client IDs to use the Google Platform Library from now until the March 30th, 2023 deprecation date. - /// See: https://github.com/flutter/flutter/issues/88084 - external String? get plugin_name; - external set plugin_name(String? v); -} - -@JS('gapi.auth2.SigninOptionsBuilder') -class SigninOptionsBuilder { - external dynamic setAppPackageName(String name); - external dynamic setFetchBasicProfile(bool fetch); - external dynamic setPrompt(String prompt); - external dynamic setScope(String scope); - external dynamic setLoginHint(String hint); -} - -@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); -} - -/// Reference: https://developers.google.com/api-client-library/javascript/reference/referencedocs#gapiauth2authorizeconfig -@anonymous -@JS() -abstract class AuthorizeConfig { - 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}); - 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); -} - -/// Reference: https://developers.google.com/api-client-library/javascript/reference/referencedocs#gapiauth2authorizeresponse -@anonymous -@JS() -abstract class AuthorizeResponse { - 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}); - 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); -} - -/// 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. - // This returns an empty JS object when the user hasn't attempted to sign in. - external AuthResponse getAuthResponse([bool includeAuthorizationData]); - - /// Returns true if the user granted the specified scopes. - external bool hasGrantedScopes(String scopes); - - // Has the API for grant and grantOfflineAccess changed? - /// Request additional scopes to the user. - /// - /// See GoogleAuth.signIn() for the list of parameters and the error code. - 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 { - /// Forces a refresh of the access token, and then returns a Promise for the new AuthResponse. - external Promise reloadAuthResponse(); -} - -extension GoogleUserExtensions on GoogleUser { - Future reloadAuthResponse() { - final _GoogleUser tt = this as _GoogleUser; - 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 Function(AuthorizeResponse response) callback); -// 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 Function(void Function(T result) resolve, Function reject) executor); -} 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 deleted file mode 100644 index 57b91838b8f1..000000000000 --- a/packages/google_sign_in/google_sign_in_web/lib/src/load_gapi.dart +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2013 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. - -@JS() -library gapi_onload; - -import 'dart:async'; - -import 'package:flutter/foundation.dart' show visibleForTesting; -import 'package:js/js.dart'; - -import 'js_interop/gapi.dart' as gapi; -import 'utils.dart' show injectJSLibraries; - -@JS() -external set gapiOnloadCallback(Function callback); - -// This name must match the external setter above -/// This is only exposed for testing. It shouldn't be accessed by users of the -/// plugin as it could break at any point. -@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), - ...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/people.dart b/packages/google_sign_in/google_sign_in_web/lib/src/people.dart new file mode 100644 index 000000000000..528dc89b1a75 --- /dev/null +++ b/packages/google_sign_in/google_sign_in_web/lib/src/people.dart @@ -0,0 +1,152 @@ +// Copyright 2013 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. + +import 'dart:convert'; + +import 'package:flutter/foundation.dart'; +import 'package:google_identity_services_web/oauth2.dart'; +import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart'; +import 'package:http/http.dart' as http; + +/// Basic scopes for self-id +const List scopes = [ + 'https://www.googleapis.com/auth/userinfo.profile', + 'https://www.googleapis.com/auth/userinfo.email', +]; + +/// People API to return my profile info... +const String MY_PROFILE = 'https://content-people.googleapis.com/v1/people/me' + '?sources=READ_SOURCE_TYPE_PROFILE' + '&personFields=photos%2Cnames%2CemailAddresses'; + +/// Requests user data from the People API using the given [tokenResponse]. +Future requestUserData( + TokenResponse tokenResponse, { + @visibleForTesting http.Client? overrideClient, +}) async { + // Request my profile from the People API. + final Map person = await _doRequest( + MY_PROFILE, + tokenResponse, + overrideClient: overrideClient, + ); + + // Now transform the Person response into a GoogleSignInUserData. + return extractUserData(person); +} + +/// Extracts user data from a Person resource. +/// +/// See: https://developers.google.com/people/api/rest/v1/people#Person +GoogleSignInUserData? extractUserData(Map json) { + final String? userId = _extractUserId(json); + final String? email = _extractPrimaryField( + json['emailAddresses'] as List?, + 'value', + ); + + assert(userId != null); + assert(email != null); + + return GoogleSignInUserData( + id: userId!, + email: email!, + displayName: _extractPrimaryField( + json['names'] as List?, + 'displayName', + ), + photoUrl: _extractPrimaryField( + json['photos'] as List?, + 'url', + ), + // Synthetic user data doesn't contain an idToken! + ); +} + +/// Extracts the ID from a Person resource. +/// +/// The User ID looks like this: +/// { +/// 'resourceName': 'people/PERSON_ID', +/// ... +/// } +String? _extractUserId(Map profile) { + final String? resourceName = profile['resourceName'] as String?; + return resourceName?.split('/').last; +} + +/// Extracts the [fieldName] marked as 'primary' from a list of [values]. +/// +/// Values can be one of: +/// * `emailAddresses` +/// * `names` +/// * `photos` +/// +/// From a Person object. +T? _extractPrimaryField(List? values, String fieldName) { + if (values != null) { + for (final Object? value in values) { + if (value != null && value is Map) { + final bool isPrimary = _extractPath( + value, + path: ['metadata', 'primary'], + defaultValue: false, + ); + if (isPrimary) { + return value[fieldName] as T?; + } + } + } + } + + return null; +} + +/// Attempts to get the property in [path] of type `T` from a deeply nested [source]. +/// +/// Returns [default] if the property is not found. +T _extractPath( + Map source, { + required List path, + required T defaultValue, +}) { + final String valueKey = path.removeLast(); + Object? data = source; + for (final String key in path) { + if (data != null && data is Map) { + data = data[key]; + } else { + break; + } + } + if (data != null && data is Map) { + return (data[valueKey] ?? defaultValue) as T; + } else { + return defaultValue; + } +} + +/// Gets from [url] with an authorization header defined by [token]. +/// +/// Attempts to [jsonDecode] the result. +Future> _doRequest( + String url, + TokenResponse token, { + http.Client? overrideClient, +}) async { + final Uri uri = Uri.parse(url); + final http.Client client = overrideClient ?? http.Client(); + try { + final http.Response response = + await client.get(uri, headers: { + 'Authorization': '${token.token_type} ${token.access_token}', + }); + if (response.statusCode != 200) { + throw http.ClientException(response.body, uri); + } + return jsonDecode(response.body) as Map; + } finally { + client.close(); + } +} 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 index 45acb1ffd7ed..c4bb9d403d2d 100644 --- 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 @@ -2,59 +2,87 @@ // 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 'dart:convert'; +import 'package:google_identity_services_web/id.dart'; +import 'package:google_identity_services_web/oauth2.dart'; import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart'; -import 'js_interop/gapiauth2.dart' as auth2; - -/// Injects a list of JS [libraries] as `script` tags into a [target] [html.HtmlElement]. -/// -/// If [target] is not provided, it defaults to the web app's `head` tag (see `web/index.html`). -/// [libraries] is a list of URLs that are used as the `src` attribute of `script` tags -/// to which an `onLoad` listener is attached (one per URL). -/// -/// Returns a [Future] that resolves when all of the `script` tags `onLoad` events trigger. -Future injectJSLibraries( - List libraries, { - html.HtmlElement? target, -}) { - final List> loading = >[]; - final List tags = []; - - final html.Element targetElement = target ?? html.querySelector('head')!; - - for (final String library in libraries) { - final html.ScriptElement script = html.ScriptElement() - ..async = true - ..defer = true - // ignore: unsafe_html - ..src = library; - // TODO(ditman): add a timeout race to fail this future - loading.add(script.onLoad.first); - tags.add(script); +/// A codec that can encode/decode JWT payloads. +/// +/// See https://www.rfc-editor.org/rfc/rfc7519#section-3 +final Codec jwtCodec = json.fuse(utf8).fuse(base64); + +/// A RegExp that can match, and extract parts from a JWT Token. +/// +/// A JWT token consists of 3 base-64 encoded parts of data separated by periods: +/// +/// header.payload.signature +/// +/// More info: https://regexr.com/789qc +final RegExp jwtTokenRegexp = RegExp( + r'^(?

[^\.\s]+)\.(?[^\.\s]+)\.(?[^\.\s]+)$'); + +/// Decodes the `claims` of a JWT token and returns them as a Map. +/// +/// JWT `claims` are stored as a JSON object in the `payload` part of the token. +/// +/// (This method does not validate the signature of the token.) +/// +/// See https://www.rfc-editor.org/rfc/rfc7519#section-3 +Map? getJwtTokenPayload(String? token) { + if (token != null) { + final RegExpMatch? match = jwtTokenRegexp.firstMatch(token); + if (match != null) { + return decodeJwtPayload(match.namedGroup('payload')); + } } - targetElement.children.addAll(tags); - return Future.wait(loading); + return null; } -/// Utility method that converts `currentUser` to the equivalent [GoogleSignInUserData]. +/// Decodes a JWT payload using the [jwtCodec]. +Map? decodeJwtPayload(String? payload) { + try { + // Payload must be normalized before passing it to the codec + return jwtCodec.decode(base64.normalize(payload!)) as Map?; + } catch (_) { + // Do nothing, we always return null for any failure. + } + return null; +} + +/// Converts a [CredentialResponse] into a [GoogleSignInUserData]. /// -/// This method returns `null` when the [currentUser] is not signed in. -GoogleSignInUserData? gapiUserToPluginUserData(auth2.GoogleUser? currentUser) { - final bool isSignedIn = currentUser?.isSignedIn() ?? false; - final auth2.BasicProfile? profile = currentUser?.getBasicProfile(); - if (!isSignedIn || profile?.getId() == null) { +/// May return `null`, if the `credentialResponse` is null, or its `credential` +/// cannot be decoded. +GoogleSignInUserData? gisResponsesToUserData( + CredentialResponse? credentialResponse) { + if (credentialResponse == null || credentialResponse.credential == null) { + return null; + } + + final Map? payload = + getJwtTokenPayload(credentialResponse.credential); + + if (payload == null) { return null; } return GoogleSignInUserData( - displayName: profile?.getName(), - email: profile?.getEmail() ?? '', - id: profile?.getId() ?? '', - photoUrl: profile?.getImageUrl(), - idToken: currentUser?.getAuthResponse().id_token, + email: payload['email']! as String, + id: payload['sub']! as String, + displayName: payload['name']! as String, + photoUrl: payload['picture']! as String, + idToken: credentialResponse.credential, + ); +} + +/// Converts responses from the GIS library into TokenData for the plugin. +GoogleSignInTokenData gisResponsesToTokenData( + CredentialResponse? credentialResponse, TokenResponse? tokenResponse) { + return GoogleSignInTokenData( + idToken: credentialResponse?.credential, + accessToken: tokenResponse?.access_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 index a9d39471c3ed..40e8b0381e67 100644 --- a/packages/google_sign_in/google_sign_in_web/pubspec.yaml +++ b/packages/google_sign_in/google_sign_in_web/pubspec.yaml @@ -3,10 +3,10 @@ description: Flutter plugin for Google Sign-In, a secure authentication system for signing in with a Google account on Android, iOS and Web. repository: https://github.com/flutter/plugins/tree/main/packages/google_sign_in/google_sign_in_web issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+google_sign_in%22 -version: 0.10.2+1 +version: 0.11.0 environment: - sdk: ">=2.12.0 <3.0.0" + sdk: ">=2.17.0 <3.0.0" flutter: ">=3.0.0" flutter: @@ -22,7 +22,9 @@ dependencies: sdk: flutter flutter_web_plugins: sdk: flutter + google_identity_services_web: ^0.2.0 google_sign_in_platform_interface: ^2.2.0 + http: ^0.13.5 js: ^0.6.3 dev_dependencies: From 9a3a77e6c32bbaa7cfd32eda5a34d9f6b51429a0 Mon Sep 17 00:00:00 2001 From: Victoria Ashworth Date: Fri, 17 Feb 2023 09:48:02 -0600 Subject: [PATCH 110/130] [image_picker] Fix images changing to incorrect orientation (#7187) * fix orientation issue * update changelog * add test * fix formatting --- .../image_picker_ios/CHANGELOG.md | 4 ++ .../ios/Runner.xcodeproj/project.pbxproj | 6 +++ .../example/ios/RunnerTests/ImageUtilTests.m | 17 ++++++-- .../PickerSaveImageToPathOperationTests.m | 37 ++++++++++++++++++ .../jpgImageWithRightOrientation.jpg | Bin 0 -> 3375 bytes .../FLTPHPickerSaveImageToPathOperation.m | 2 +- .../image_picker_ios/pubspec.yaml | 2 +- 7 files changed, 62 insertions(+), 6 deletions(-) create mode 100644 packages/image_picker/image_picker_ios/example/ios/TestImages/jpgImageWithRightOrientation.jpg diff --git a/packages/image_picker/image_picker_ios/CHANGELOG.md b/packages/image_picker/image_picker_ios/CHANGELOG.md index b75e6333b94e..dbd5160edd7d 100644 --- a/packages/image_picker/image_picker_ios/CHANGELOG.md +++ b/packages/image_picker/image_picker_ios/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.8.6+8 + +* Fixes issue with images sometimes changing to incorrect orientation. + ## 0.8.6+7 * Fixes issue where GIF file would not animate without `Photo Library Usage` permissions. Fixes issue where PNG and GIF files were converted to JPG, but only when they are do not have `Photo Library Usage` permissions. diff --git a/packages/image_picker/image_picker_ios/example/ios/Runner.xcodeproj/project.pbxproj b/packages/image_picker/image_picker_ios/example/ios/Runner.xcodeproj/project.pbxproj index 2c97bd1d667f..ddbc856d6aa7 100644 --- a/packages/image_picker/image_picker_ios/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/image_picker/image_picker_ios/example/ios/Runner.xcodeproj/project.pbxproj @@ -18,6 +18,8 @@ 680049382280F2B9006DD6AB /* pngImage.png in Resources */ = {isa = PBXBuildFile; fileRef = 680049352280F2B8006DD6AB /* pngImage.png */; }; 680049392280F2B9006DD6AB /* jpgImage.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 680049362280F2B8006DD6AB /* jpgImage.jpg */; }; 6801C8392555D726009DAF8D /* ImagePickerFromGalleryUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6801C8382555D726009DAF8D /* ImagePickerFromGalleryUITests.m */; }; + 782C2B45299ECE33008DC703 /* jpgImageWithRightOrientation.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 782C2B44299ECE33008DC703 /* jpgImageWithRightOrientation.jpg */; }; + 782C2B46299ECE33008DC703 /* jpgImageWithRightOrientation.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 782C2B44299ECE33008DC703 /* jpgImageWithRightOrientation.jpg */; }; 7865C5E12941326F0010E17F /* bmpImage.bmp in Resources */ = {isa = PBXBuildFile; fileRef = 7865C5E02941326F0010E17F /* bmpImage.bmp */; }; 7865C5E22941326F0010E17F /* bmpImage.bmp in Resources */ = {isa = PBXBuildFile; fileRef = 7865C5E02941326F0010E17F /* bmpImage.bmp */; }; 7865C5E4294132D50010E17F /* svgImage.svg in Resources */ = {isa = PBXBuildFile; fileRef = 7865C5E3294132D50010E17F /* svgImage.svg */; }; @@ -95,6 +97,7 @@ 6801C83A2555D726009DAF8D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 68B9AF71243E4B3F00927CE4 /* ImagePickerPluginTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ImagePickerPluginTests.m; sourceTree = ""; }; 68F4B463228B3AB500C25614 /* PhotoAssetUtilTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PhotoAssetUtilTests.m; sourceTree = ""; }; + 782C2B44299ECE33008DC703 /* jpgImageWithRightOrientation.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = jpgImageWithRightOrientation.jpg; sourceTree = ""; }; 7865C5E02941326F0010E17F /* bmpImage.bmp */ = {isa = PBXFileReference; lastKnownFileType = image.bmp; path = bmpImage.bmp; sourceTree = ""; }; 7865C5E3294132D50010E17F /* svgImage.svg */ = {isa = PBXFileReference; lastKnownFileType = text; path = svgImage.svg; sourceTree = ""; }; 7865C5E62941374F0010E17F /* heicImage.heic */ = {isa = PBXFileReference; lastKnownFileType = file; path = heicImage.heic; sourceTree = ""; }; @@ -169,6 +172,7 @@ 680049282280E33D006DD6AB /* TestImages */ = { isa = PBXGroup; children = ( + 782C2B44299ECE33008DC703 /* jpgImageWithRightOrientation.jpg */, 86E9A88F272747B90017E6E0 /* webpImage.webp */, 9FC8F0E8229FA49E00C8D58F /* gifImage.gif */, 680049362280F2B8006DD6AB /* jpgImage.jpg */, @@ -398,6 +402,7 @@ 86E9A894272754A30017E6E0 /* webpImage.webp in Resources */, 86E9A895272769130017E6E0 /* pngImage.png in Resources */, 7865C5FC294157BC0010E17F /* icnsImage.icns in Resources */, + 782C2B45299ECE33008DC703 /* jpgImageWithRightOrientation.jpg in Resources */, 86E9A896272769150017E6E0 /* jpgImage.jpg in Resources */, 7865C5ED294137AB0010E17F /* tiffImage.tiff in Resources */, ); @@ -409,6 +414,7 @@ files = ( 9FC8F0EC229FA68500C8D58F /* gifImage.gif in Resources */, 7865C5EE294137AB0010E17F /* tiffImage.tiff in Resources */, + 782C2B46299ECE33008DC703 /* jpgImageWithRightOrientation.jpg in Resources */, 7865C5E82941374F0010E17F /* heicImage.heic in Resources */, 7865C5FD294157BC0010E17F /* icnsImage.icns in Resources */, 680049382280F2B9006DD6AB /* pngImage.png in Resources */, diff --git a/packages/image_picker/image_picker_ios/example/ios/RunnerTests/ImageUtilTests.m b/packages/image_picker/image_picker_ios/example/ios/RunnerTests/ImageUtilTests.m index e449a84b80bb..1dc807a15dba 100644 --- a/packages/image_picker/image_picker_ios/example/ios/RunnerTests/ImageUtilTests.m +++ b/packages/image_picker/image_picker_ios/example/ios/RunnerTests/ImageUtilTests.m @@ -36,12 +36,21 @@ - (void)testScaledImage_ShouldBeScaledWithNoMetadata { } - (void)testScaledImage_ShouldBeCorrectRotation { - UIImage *image = [UIImage imageWithData:ImagePickerTestImages.JPGTestData]; + NSURL *imageURL = + [[NSBundle bundleForClass:[self class]] URLForResource:@"jpgImageWithRightOrientation" + withExtension:@"jpg"]; + NSData *imageData = [NSData dataWithContentsOfURL:imageURL]; + UIImage *image = [UIImage imageWithData:imageData]; + XCTAssertEqual(image.size.width, 130); + XCTAssertEqual(image.size.height, 174); + XCTAssertEqual(image.imageOrientation, UIImageOrientationRight); + UIImage *newImage = [FLTImagePickerImageUtil scaledImage:image - maxWidth:@3 - maxHeight:@2 + maxWidth:@10 + maxHeight:@10 isMetadataAvailable:YES]; - + XCTAssertEqual(newImage.size.width, 10); + XCTAssertEqual(newImage.size.height, 7); XCTAssertEqual(newImage.imageOrientation, UIImageOrientationUp); } diff --git a/packages/image_picker/image_picker_ios/example/ios/RunnerTests/PickerSaveImageToPathOperationTests.m b/packages/image_picker/image_picker_ios/example/ios/RunnerTests/PickerSaveImageToPathOperationTests.m index 027e287d1586..57ccb86c0060 100644 --- a/packages/image_picker/image_picker_ios/example/ios/RunnerTests/PickerSaveImageToPathOperationTests.m +++ b/packages/image_picker/image_picker_ios/example/ios/RunnerTests/PickerSaveImageToPathOperationTests.m @@ -101,6 +101,43 @@ - (void)testSaveHEICImage API_AVAILABLE(ios(14)) { [self verifySavingImageWithPickerResult:result fullMetadata:YES withExtension:@"jpg"]; } +- (void)testSaveWithOrientation API_AVAILABLE(ios(14)) { + NSURL *imageURL = + [[NSBundle bundleForClass:[self class]] URLForResource:@"jpgImageWithRightOrientation" + withExtension:@"jpg"]; + NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithContentsOfURL:imageURL]; + PHPickerResult *result = [self createPickerResultWithProvider:itemProvider]; + + XCTestExpectation *pathExpectation = [self expectationWithDescription:@"Path was created"]; + XCTestExpectation *operationExpectation = + [self expectationWithDescription:@"Operation completed"]; + + FLTPHPickerSaveImageToPathOperation *operation = [[FLTPHPickerSaveImageToPathOperation alloc] + initWithResult:result + maxHeight:@10 + maxWidth:@10 + desiredImageQuality:@100 + fullMetadata:NO + savedPathBlock:^(NSString *savedPath, FlutterError *error) { + XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:savedPath]); + + // Ensure image retained it's orientation data. + XCTAssertEqualObjects([NSURL URLWithString:savedPath].pathExtension, @"jpg"); + UIImage *image = [UIImage imageWithContentsOfFile:savedPath]; + XCTAssertEqual(image.imageOrientation, UIImageOrientationRight); + XCTAssertEqual(image.size.width, 7); + XCTAssertEqual(image.size.height, 10); + [pathExpectation fulfill]; + }]; + operation.completionBlock = ^{ + [operationExpectation fulfill]; + }; + + [operation start]; + [self waitForExpectationsWithTimeout:30 handler:nil]; + XCTAssertTrue(operation.isFinished); +} + - (void)testSaveICNSImage API_AVAILABLE(ios(14)) { NSURL *imageURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"icnsImage" withExtension:@"icns"]; diff --git a/packages/image_picker/image_picker_ios/example/ios/TestImages/jpgImageWithRightOrientation.jpg b/packages/image_picker/image_picker_ios/example/ios/TestImages/jpgImageWithRightOrientation.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2b3eaf5e294463db3fa77fd19cfa298f38cc671c GIT binary patch literal 3375 zcmdT`dsGu=7QZu-2Ly^d1{W=86tM|ME05Ghwz~o`Jfc8CkosCzKpyc327;)Ztpt?1 zO{*0ZA1rF*74;O)p<>l`J!(~ScZ($vD57o^m5PtH*6OwvJ9}sHK-xo3cmM0n-1&a_ za_{}_{U-DMF4MzY2X9qeVjK_zKmvY%xxv4pT&>FiNJ@fG0B{2V;R_TZ0t9#)3w#a( zq!TZ?@M1#Zupt3RKL^I38+5>P*kepMg^H_SEaxcP3?RgBys(If@G8QN36cV{b3QyQ z%uLuLG7Ny3bVWR5rd+t9LEu;wgK;7tD96mOya}(Nyou4n@+O?oh;gKA72aV-ykU7W zJd3d=C_yX7buLjID%vr>o z0v|V64Q7g%2&4}|`4G%mq7L_&&n3uiHz$}UCMI-q7kPLCNl+BYqxhIFTE4+Kh>?m5ZT;=o*+MYHIw^REOYwD+W~RLo?$p9>l^ zC~vZ2vA4xklA{LpL6~aymKrC0zKK7pw4>C1U(8s9NbJ#b|IojVo z=lw~y-S*N{HrD-T^_%3C@n`Z|Po$Lg2268WjWCLqW*f5bw?WjfEPYWio<&m-t5+54 zS^O8obP*b}@kuQgVJT@_j!N#_lWS#N4uJhczlmkm9$eX^D`PHcMgtwlienEFc)8_uBQ&VM|zO_`% zX$#%Vmyhf0)tqjk?nbuHoLE!XTRu-EsVm>0D)~y4PZLbXATef8nsSmp5gOT^d|evQ zV6L^M5lYImxXBnOZxGiI=LNfKJMS7dnIs3nGOZB_WXrDF#}=wavG?OG(E(KR^a_JA z%*8Rt9Wxp0AiN~o*-k&EKOm-CkJ@&}FZf$|5a#lpyeaEM`gM6tbYY@$G}M`-MwTj#tv@#*yfdokvPU zcL(9E)z4^{l)1UqmezwzaS&IRaM~upqFvPNr0DTX=aB19Ia5;KY-c+*dxsKC*9Z=Q z*62Rtq`WL|-)!e^Be&8cA@*Hhx4pH+Z_Yzf69n_uIQVZzy{neEj?(HG8b`0~`P_Uoo XujyXbU5>g#?Q4=WIXfA9H*@*F9kTcL literal 0 HcmV?d00001 diff --git a/packages/image_picker/image_picker_ios/ios/Classes/FLTPHPickerSaveImageToPathOperation.m b/packages/image_picker/image_picker_ios/ios/Classes/FLTPHPickerSaveImageToPathOperation.m index efcbdbeec897..80e03ddd6578 100644 --- a/packages/image_picker/image_picker_ios/ios/Classes/FLTPHPickerSaveImageToPathOperation.m +++ b/packages/image_picker/image_picker_ios/ios/Classes/FLTPHPickerSaveImageToPathOperation.m @@ -135,7 +135,7 @@ - (void)processImage:(NSData *)pickerImageData API_AVAILABLE(ios(14)) { localImage = [FLTImagePickerImageUtil scaledImage:localImage maxWidth:self.maxWidth maxHeight:self.maxHeight - isMetadataAvailable:originalAsset != nil]; + isMetadataAvailable:YES]; } if (originalAsset) { void (^resultHandler)(NSData *imageData, NSString *dataUTI, NSDictionary *info) = diff --git a/packages/image_picker/image_picker_ios/pubspec.yaml b/packages/image_picker/image_picker_ios/pubspec.yaml index 1f4e2af4cb96..b188055087c7 100755 --- a/packages/image_picker/image_picker_ios/pubspec.yaml +++ b/packages/image_picker/image_picker_ios/pubspec.yaml @@ -2,7 +2,7 @@ name: image_picker_ios description: iOS implementation of the image_picker plugin. repository: https://github.com/flutter/plugins/tree/main/packages/image_picker/image_picker_ios issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+image_picker%22 -version: 0.8.6+7 +version: 0.8.6+8 environment: sdk: ">=2.14.0 <3.0.0" From 8f3419be5e0ea8a003f8dd9b89fd7f6f0d0037e1 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Fri, 17 Feb 2023 11:14:21 -0500 Subject: [PATCH 111/130] Roll Flutter from 22e17bb71050 to 298d8c76ba78 (20 revisions) (#7190) * f85438bfa c8b1d2ffa Roll Fuchsia Mac SDK from YpQKlqmyn8r_snx06... to xl9Y8o-9FDyvPogki... (flutter/engine#39675) (flutter/flutter#120887) * 174a562a6 d699b4a91 Roll Flutter from e3471f08d1d3 to df41e58f6f4e (83 revisions) (flutter/plugins#7184) (flutter/flutter#120888) * 170539f83 Roll Flutter Engine from c8b1d2ffaec8 to 0d8d93306822 (2 revisions) (flutter/flutter#120891) * df98689c9 2be7253c9 Roll Fuchsia Linux SDK from q7u2WyX2BSRBIzyTW... to yT4JLKTCWWwbRwB0l... (flutter/engine#39679) (flutter/flutter#120898) * cacef57b6 [flutter_tools] Skip over "Resolving dependencies..." text in integration tests (flutter/flutter#120077) * 34102ca3b Migrate channels to pkg:integration _test (flutter/flutter#120833) * df13ea248 Roll Flutter Engine from 2be7253c9b10 to e4cb80e22ee1 (2 revisions) (flutter/flutter#120903) * a2e65f7c3 Roll Flutter Engine from e4cb80e22ee1 to 4a90fbcd6901 (2 revisions) (flutter/flutter#120911) * e00241a06 Enable Windows plugin tests (flutter/flutter#119345) * 09ad9f3cd Document ScrollPhysics invariant requiring ballistic motion (flutter/flutter#120400) * 6029de2fb Update switch template (flutter/flutter#120919) * 229d70ea3 Roll Flutter Engine from 4a90fbcd6901 to bddfc1c4dcaa (5 revisions) (flutter/flutter#120920) * 206c6ae99 roll packages (flutter/flutter#120922) * 9fcaaebb5 Roll Flutter Engine from bddfc1c4dcaa to 6602fc753525 (3 revisions) (flutter/flutter#120928) * 00c0a07fa Increase Linux docs_test timeout (flutter/flutter#120899) * e29a79975 946b29198 [dart:ui] Introduce `PlatformDispatcher.implicitView` (flutter/engine#39553) (flutter/flutter#120939) * 081cd5776 650db7a72 [macOS] Eliminate mirrors support (flutter/engine#39694) (flutter/flutter#120943) * 875e48c69 52a4fb4c5 Roll Skia from b1800a8b9595 to d0df677ffd5e (13 revisions) (flutter/engine#39699) (flutter/flutter#120947) * 78d058f46 6e92c0c28 Roll Fuchsia Mac SDK from xl9Y8o-9FDyvPogki... to haDvcC5VzWVdQs9Rs... (flutter/engine#39700) (flutter/flutter#120950) * 298d8c76b Revert "Remove references to Observatory (#118577)" (flutter/flutter#120929) --- .ci/flutter_master.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/flutter_master.version b/.ci/flutter_master.version index 2369cb606e19..b0f7e4c85b98 100644 --- a/.ci/flutter_master.version +++ b/.ci/flutter_master.version @@ -1 +1 @@ -22e17bb71050fb6bc18c95fede935fa4715772e8 +298d8c76ba78007deb5b96f320a11ccefe97a794 From 3c7d54bba0d9a11326c884953973db8a6280de6b Mon Sep 17 00:00:00 2001 From: Camille Simon <43054281+camsim99@users.noreply.github.com> Date: Fri, 17 Feb 2023 11:38:39 -0800 Subject: [PATCH 112/130] [camerax] Implement camera preview (#7112) * Add base code from proof of concept * Fix analyzer * Add example app and tests * Actually add tets and visible for testing annotation * Make methods private * Fix tests * Fix analyze: * Update changelog * Formatting * Update todos with links and modify camera controller * Format and add availableCameras * Fix mocks * Try bumping mockito version * Review documentation * Re-generate mocks * Fix typo * Fix another typo * Address review and fix bug * Fix analyze * Fix tests * Foramtting * Clear preview is paused * Add unknown lens --- .../camera_android_camerax/CHANGELOG.md | 3 +- .../example/lib/camera_controller.dart | 957 +++++++++++++++ .../example/lib/camera_image.dart | 177 +++ .../example/lib/camera_preview.dart | 81 ++ .../example/lib/main.dart | 1047 ++++++++++++++++- .../example/pubspec.yaml | 1 + .../lib/src/android_camera_camerax.dart | 325 ++++- .../lib/src/camera_selector.dart | 22 +- .../lib/src/system_services.dart | 2 - .../camera_android_camerax/pubspec.yaml | 3 +- .../test/android_camera_camerax_test.dart | 363 +++++- .../android_camera_camerax_test.mocks.dart | 370 +++++- .../test/camera_selector_test.dart | 8 +- 13 files changed, 3264 insertions(+), 95 deletions(-) create mode 100644 packages/camera/camera_android_camerax/example/lib/camera_controller.dart create mode 100644 packages/camera/camera_android_camerax/example/lib/camera_image.dart create mode 100644 packages/camera/camera_android_camerax/example/lib/camera_preview.dart diff --git a/packages/camera/camera_android_camerax/CHANGELOG.md b/packages/camera/camera_android_camerax/CHANGELOG.md index 06f41a68a146..9e6c5a901fc9 100644 --- a/packages/camera/camera_android_camerax/CHANGELOG.md +++ b/packages/camera/camera_android_camerax/CHANGELOG.md @@ -9,5 +9,6 @@ * Bump CameraX version to 1.3.0-alpha03 and Kotlin version to 1.8.0. * Changes instance manager to allow the separate creation of identical objects. * Adds Preview and Surface classes, along with other methods needed to implement camera preview. -* Adds implementation of availableCameras() +* Adds implementation of availableCameras(). +* Implements camera preview, createCamera, initializeCamera, onCameraError, onDeviceOrientationChanged, and onCameraInitialized. * Adds integration test to plugin. diff --git a/packages/camera/camera_android_camerax/example/lib/camera_controller.dart b/packages/camera/camera_android_camerax/example/lib/camera_controller.dart new file mode 100644 index 000000000000..b1b5e9d4ceb9 --- /dev/null +++ b/packages/camera/camera_android_camerax/example/lib/camera_controller.dart @@ -0,0 +1,957 @@ +// Copyright 2013 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. + +import 'dart:async'; +import 'dart:collection'; +import 'dart:math'; + +import 'package:camera_platform_interface/camera_platform_interface.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +import 'camera_image.dart'; + +/// Signature for a callback receiving the a camera image. +/// +/// This is used by [CameraController.startImageStream]. +// TODO(stuartmorgan): Fix this naming the next time there's a breaking change +// to this package. +// ignore: camel_case_types +typedef onLatestImageAvailable = Function(CameraImage image); + +/// Completes with a list of available cameras. +/// +/// May throw a [CameraException]. +Future> availableCameras() async { + return CameraPlatform.instance.availableCameras(); +} + +// TODO(stuartmorgan): Remove this once the package requires 2.10, where the +// dart:async `unawaited` accepts a nullable future. +void _unawaited(Future? future) {} + +/// The state of a [CameraController]. +class CameraValue { + /// Creates a new camera controller state. + const CameraValue({ + required this.isInitialized, + this.errorDescription, + this.previewSize, + required this.isRecordingVideo, + required this.isTakingPicture, + required this.isStreamingImages, + required bool isRecordingPaused, + required this.flashMode, + required this.exposureMode, + required this.focusMode, + required this.exposurePointSupported, + required this.focusPointSupported, + required this.deviceOrientation, + this.lockedCaptureOrientation, + this.recordingOrientation, + this.isPreviewPaused = false, + this.previewPauseOrientation, + }) : _isRecordingPaused = isRecordingPaused; + + /// Creates a new camera controller state for an uninitialized controller. + const CameraValue.uninitialized() + : this( + isInitialized: false, + isRecordingVideo: false, + isTakingPicture: false, + isStreamingImages: false, + isRecordingPaused: false, + flashMode: FlashMode.auto, + exposureMode: ExposureMode.auto, + exposurePointSupported: false, + focusMode: FocusMode.auto, + focusPointSupported: false, + deviceOrientation: DeviceOrientation.portraitUp, + isPreviewPaused: false, + ); + + /// True after [CameraController.initialize] has completed successfully. + final bool isInitialized; + + /// True when a picture capture request has been sent but as not yet returned. + final bool isTakingPicture; + + /// True when the camera is recording (not the same as previewing). + final bool isRecordingVideo; + + /// True when images from the camera are being streamed. + final bool isStreamingImages; + + final bool _isRecordingPaused; + + /// True when the preview widget has been paused manually. + final bool isPreviewPaused; + + /// Set to the orientation the preview was paused in, if it is currently paused. + final DeviceOrientation? previewPauseOrientation; + + /// True when camera [isRecordingVideo] and recording is paused. + bool get isRecordingPaused => isRecordingVideo && _isRecordingPaused; + + /// Description of an error state. + /// + /// This is null while the controller is not in an error state. + /// When [hasError] is true this contains the error description. + final String? errorDescription; + + /// The size of the preview in pixels. + /// + /// Is `null` until [isInitialized] is `true`. + final Size? previewSize; + + /// Convenience getter for `previewSize.width / previewSize.height`. + /// + /// Can only be called when [initialize] is done. + double get aspectRatio => previewSize!.width / previewSize!.height; + + /// Whether the controller is in an error state. + /// + /// When true [errorDescription] describes the error. + bool get hasError => errorDescription != null; + + /// The flash mode the camera is currently set to. + final FlashMode flashMode; + + /// The exposure mode the camera is currently set to. + final ExposureMode exposureMode; + + /// The focus mode the camera is currently set to. + final FocusMode focusMode; + + /// Whether setting the exposure point is supported. + final bool exposurePointSupported; + + /// Whether setting the focus point is supported. + final bool focusPointSupported; + + /// The current device UI orientation. + final DeviceOrientation deviceOrientation; + + /// The currently locked capture orientation. + final DeviceOrientation? lockedCaptureOrientation; + + /// Whether the capture orientation is currently locked. + bool get isCaptureOrientationLocked => lockedCaptureOrientation != null; + + /// The orientation of the currently running video recording. + final DeviceOrientation? recordingOrientation; + + /// Creates a modified copy of the object. + /// + /// Explicitly specified fields get the specified value, all other fields get + /// the same value of the current object. + CameraValue copyWith({ + bool? isInitialized, + bool? isRecordingVideo, + bool? isTakingPicture, + bool? isStreamingImages, + String? errorDescription, + Size? previewSize, + bool? isRecordingPaused, + FlashMode? flashMode, + ExposureMode? exposureMode, + FocusMode? focusMode, + bool? exposurePointSupported, + bool? focusPointSupported, + DeviceOrientation? deviceOrientation, + Optional? lockedCaptureOrientation, + Optional? recordingOrientation, + bool? isPreviewPaused, + Optional? previewPauseOrientation, + }) { + return CameraValue( + isInitialized: isInitialized ?? this.isInitialized, + errorDescription: errorDescription, + previewSize: previewSize ?? this.previewSize, + isRecordingVideo: isRecordingVideo ?? this.isRecordingVideo, + isTakingPicture: isTakingPicture ?? this.isTakingPicture, + isStreamingImages: isStreamingImages ?? this.isStreamingImages, + isRecordingPaused: isRecordingPaused ?? _isRecordingPaused, + flashMode: flashMode ?? this.flashMode, + exposureMode: exposureMode ?? this.exposureMode, + focusMode: focusMode ?? this.focusMode, + exposurePointSupported: + exposurePointSupported ?? this.exposurePointSupported, + focusPointSupported: focusPointSupported ?? this.focusPointSupported, + deviceOrientation: deviceOrientation ?? this.deviceOrientation, + lockedCaptureOrientation: lockedCaptureOrientation == null + ? this.lockedCaptureOrientation + : lockedCaptureOrientation.orNull, + recordingOrientation: recordingOrientation == null + ? this.recordingOrientation + : recordingOrientation.orNull, + isPreviewPaused: isPreviewPaused ?? this.isPreviewPaused, + previewPauseOrientation: previewPauseOrientation == null + ? this.previewPauseOrientation + : previewPauseOrientation.orNull, + ); + } + + @override + String toString() { + return '${objectRuntimeType(this, 'CameraValue')}(' + 'isRecordingVideo: $isRecordingVideo, ' + 'isInitialized: $isInitialized, ' + 'errorDescription: $errorDescription, ' + 'previewSize: $previewSize, ' + 'isStreamingImages: $isStreamingImages, ' + 'flashMode: $flashMode, ' + 'exposureMode: $exposureMode, ' + 'focusMode: $focusMode, ' + 'exposurePointSupported: $exposurePointSupported, ' + 'focusPointSupported: $focusPointSupported, ' + 'deviceOrientation: $deviceOrientation, ' + 'lockedCaptureOrientation: $lockedCaptureOrientation, ' + 'recordingOrientation: $recordingOrientation, ' + 'isPreviewPaused: $isPreviewPaused, ' + 'previewPausedOrientation: $previewPauseOrientation)'; + } +} + +/// Controls a device camera. +/// +/// Use [availableCameras] to get a list of available cameras. +/// +/// Before using a [CameraController] a call to [initialize] must complete. +/// +/// To show the camera preview on the screen use a [CameraPreview] widget. +class CameraController extends ValueNotifier { + /// Creates a new camera controller in an uninitialized state. + CameraController( + this.description, + this.resolutionPreset, { + this.enableAudio = true, + this.imageFormatGroup, + }) : super(const CameraValue.uninitialized()); + + /// The properties of the camera device controlled by this controller. + final CameraDescription description; + + /// The resolution this controller is targeting. + /// + /// This resolution preset is not guaranteed to be available on the device, + /// if unavailable a lower resolution will be used. + /// + /// See also: [ResolutionPreset]. + final ResolutionPreset resolutionPreset; + + /// Whether to include audio when recording a video. + final bool enableAudio; + + /// The [ImageFormatGroup] describes the output of the raw image format. + /// + /// When null the imageFormat will fallback to the platforms default. + final ImageFormatGroup? imageFormatGroup; + + /// The id of a camera that hasn't been initialized. + @visibleForTesting + static const int kUninitializedCameraId = -1; + int _cameraId = kUninitializedCameraId; + + bool _isDisposed = false; + StreamSubscription? _imageStreamSubscription; + FutureOr? _initCalled; + StreamSubscription? + _deviceOrientationSubscription; + + /// Checks whether [CameraController.dispose] has completed successfully. + /// + /// This is a no-op when asserts are disabled. + void debugCheckIsDisposed() { + assert(_isDisposed); + } + + /// The camera identifier with which the controller is associated. + int get cameraId => _cameraId; + + /// Initializes the camera on the device. + /// + /// Throws a [CameraException] if the initialization fails. + Future initialize() async { + if (_isDisposed) { + throw CameraException( + 'Disposed CameraController', + 'initialize was called on a disposed CameraController', + ); + } + try { + final Completer initializeCompleter = + Completer(); + + _deviceOrientationSubscription = CameraPlatform.instance + .onDeviceOrientationChanged() + .listen((DeviceOrientationChangedEvent event) { + value = value.copyWith( + deviceOrientation: event.orientation, + ); + }); + + _cameraId = await CameraPlatform.instance.createCamera( + description, + resolutionPreset, + enableAudio: enableAudio, + ); + + _unawaited(CameraPlatform.instance + .onCameraInitialized(_cameraId) + .first + .then((CameraInitializedEvent event) { + initializeCompleter.complete(event); + })); + + await CameraPlatform.instance.initializeCamera( + _cameraId, + imageFormatGroup: imageFormatGroup ?? ImageFormatGroup.unknown, + ); + + value = value.copyWith( + isInitialized: true, + previewSize: await initializeCompleter.future + .then((CameraInitializedEvent event) => Size( + event.previewWidth, + event.previewHeight, + )), + exposureMode: await initializeCompleter.future + .then((CameraInitializedEvent event) => event.exposureMode), + focusMode: await initializeCompleter.future + .then((CameraInitializedEvent event) => event.focusMode), + exposurePointSupported: await initializeCompleter.future.then( + (CameraInitializedEvent event) => event.exposurePointSupported), + focusPointSupported: await initializeCompleter.future + .then((CameraInitializedEvent event) => event.focusPointSupported), + ); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + + _initCalled = true; + } + + /// Prepare the capture session for video recording. + /// + /// Use of this method is optional, but it may be called for performance + /// reasons on iOS. + /// + /// Preparing audio can cause a minor delay in the CameraPreview view on iOS. + /// If video recording is intended, calling this early eliminates this delay + /// that would otherwise be experienced when video recording is started. + /// This operation is a no-op on Android and Web. + /// + /// Throws a [CameraException] if the prepare fails. + Future prepareForVideoRecording() async { + await CameraPlatform.instance.prepareForVideoRecording(); + } + + /// Pauses the current camera preview + Future pausePreview() async { + if (value.isPreviewPaused) { + return; + } + try { + await CameraPlatform.instance.pausePreview(_cameraId); + value = value.copyWith( + isPreviewPaused: true, + previewPauseOrientation: Optional.of( + value.lockedCaptureOrientation ?? value.deviceOrientation)); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + /// Resumes the current camera preview + Future resumePreview() async { + if (!value.isPreviewPaused) { + return; + } + try { + await CameraPlatform.instance.resumePreview(_cameraId); + value = value.copyWith( + isPreviewPaused: false, + previewPauseOrientation: const Optional.absent()); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + /// Captures an image and returns the file where it was saved. + /// + /// Throws a [CameraException] if the capture fails. + Future takePicture() async { + _throwIfNotInitialized('takePicture'); + if (value.isTakingPicture) { + throw CameraException( + 'Previous capture has not returned yet.', + 'takePicture was called before the previous capture returned.', + ); + } + try { + value = value.copyWith(isTakingPicture: true); + final XFile file = await CameraPlatform.instance.takePicture(_cameraId); + value = value.copyWith(isTakingPicture: false); + return file; + } on PlatformException catch (e) { + value = value.copyWith(isTakingPicture: false); + throw CameraException(e.code, e.message); + } + } + + /// Start streaming images from platform camera. + /// + /// Settings for capturing images on iOS and Android is set to always use the + /// latest image available from the camera and will drop all other images. + /// + /// When running continuously with [CameraPreview] widget, this function runs + /// best with [ResolutionPreset.low]. Running on [ResolutionPreset.high] can + /// have significant frame rate drops for [CameraPreview] on lower end + /// devices. + /// + /// Throws a [CameraException] if image streaming or video recording has + /// already started. + /// + /// The `startImageStream` method is only available on Android and iOS (other + /// platforms won't be supported in current setup). + /// + // TODO(bmparr): Add settings for resolution and fps. + Future startImageStream(onLatestImageAvailable onAvailable) async { + assert(defaultTargetPlatform == TargetPlatform.android || + defaultTargetPlatform == TargetPlatform.iOS); + _throwIfNotInitialized('startImageStream'); + if (value.isRecordingVideo) { + throw CameraException( + 'A video recording is already started.', + 'startImageStream was called while a video is being recorded.', + ); + } + if (value.isStreamingImages) { + throw CameraException( + 'A camera has started streaming images.', + 'startImageStream was called while a camera was streaming images.', + ); + } + + try { + _imageStreamSubscription = CameraPlatform.instance + .onStreamedFrameAvailable(_cameraId) + .listen((CameraImageData imageData) { + onAvailable(CameraImage.fromPlatformInterface(imageData)); + }); + value = value.copyWith(isStreamingImages: true); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + /// Stop streaming images from platform camera. + /// + /// Throws a [CameraException] if image streaming was not started or video + /// recording was started. + /// + /// The `stopImageStream` method is only available on Android and iOS (other + /// platforms won't be supported in current setup). + Future stopImageStream() async { + assert(defaultTargetPlatform == TargetPlatform.android || + defaultTargetPlatform == TargetPlatform.iOS); + _throwIfNotInitialized('stopImageStream'); + if (!value.isStreamingImages) { + throw CameraException( + 'No camera is streaming images', + 'stopImageStream was called when no camera is streaming images.', + ); + } + + try { + value = value.copyWith(isStreamingImages: false); + await _imageStreamSubscription?.cancel(); + _imageStreamSubscription = null; + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + /// Start a video recording. + /// + /// You may optionally pass an [onAvailable] callback to also have the + /// video frames streamed to this callback. + /// + /// The video is returned as a [XFile] after calling [stopVideoRecording]. + /// Throws a [CameraException] if the capture fails. + Future startVideoRecording( + {onLatestImageAvailable? onAvailable}) async { + _throwIfNotInitialized('startVideoRecording'); + if (value.isRecordingVideo) { + throw CameraException( + 'A video recording is already started.', + 'startVideoRecording was called when a recording is already started.', + ); + } + + Function(CameraImageData image)? streamCallback; + if (onAvailable != null) { + streamCallback = (CameraImageData imageData) { + onAvailable(CameraImage.fromPlatformInterface(imageData)); + }; + } + + try { + await CameraPlatform.instance.startVideoCapturing( + VideoCaptureOptions(_cameraId, streamCallback: streamCallback)); + value = value.copyWith( + isRecordingVideo: true, + isRecordingPaused: false, + recordingOrientation: Optional.of( + value.lockedCaptureOrientation ?? value.deviceOrientation), + isStreamingImages: onAvailable != null); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + /// Stops the video recording and returns the file where it was saved. + /// + /// Throws a [CameraException] if the capture failed. + Future stopVideoRecording() async { + _throwIfNotInitialized('stopVideoRecording'); + if (!value.isRecordingVideo) { + throw CameraException( + 'No video is recording', + 'stopVideoRecording was called when no video is recording.', + ); + } + + if (value.isStreamingImages) { + stopImageStream(); + } + + try { + final XFile file = + await CameraPlatform.instance.stopVideoRecording(_cameraId); + value = value.copyWith( + isRecordingVideo: false, + recordingOrientation: const Optional.absent(), + ); + return file; + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + /// Pause video recording. + /// + /// This feature is only available on iOS and Android sdk 24+. + Future pauseVideoRecording() async { + _throwIfNotInitialized('pauseVideoRecording'); + if (!value.isRecordingVideo) { + throw CameraException( + 'No video is recording', + 'pauseVideoRecording was called when no video is recording.', + ); + } + try { + await CameraPlatform.instance.pauseVideoRecording(_cameraId); + value = value.copyWith(isRecordingPaused: true); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + /// Resume video recording after pausing. + /// + /// This feature is only available on iOS and Android sdk 24+. + Future resumeVideoRecording() async { + _throwIfNotInitialized('resumeVideoRecording'); + if (!value.isRecordingVideo) { + throw CameraException( + 'No video is recording', + 'resumeVideoRecording was called when no video is recording.', + ); + } + try { + await CameraPlatform.instance.resumeVideoRecording(_cameraId); + value = value.copyWith(isRecordingPaused: false); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + /// Returns a widget showing a live camera preview. + Widget buildPreview() { + _throwIfNotInitialized('buildPreview'); + try { + return CameraPlatform.instance.buildPreview(_cameraId); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + /// Gets the maximum supported zoom level for the selected camera. + Future getMaxZoomLevel() { + _throwIfNotInitialized('getMaxZoomLevel'); + try { + return CameraPlatform.instance.getMaxZoomLevel(_cameraId); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + /// Gets the minimum supported zoom level for the selected camera. + Future getMinZoomLevel() { + _throwIfNotInitialized('getMinZoomLevel'); + try { + return CameraPlatform.instance.getMinZoomLevel(_cameraId); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + /// Set the zoom level for the selected camera. + /// + /// The supplied [zoom] value should be between 1.0 and the maximum supported + /// zoom level returned by the `getMaxZoomLevel`. Throws an `CameraException` + /// when an illegal zoom level is suplied. + Future setZoomLevel(double zoom) { + _throwIfNotInitialized('setZoomLevel'); + try { + return CameraPlatform.instance.setZoomLevel(_cameraId, zoom); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + /// Sets the flash mode for taking pictures. + Future setFlashMode(FlashMode mode) async { + try { + await CameraPlatform.instance.setFlashMode(_cameraId, mode); + value = value.copyWith(flashMode: mode); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + /// Sets the exposure mode for taking pictures. + Future setExposureMode(ExposureMode mode) async { + try { + await CameraPlatform.instance.setExposureMode(_cameraId, mode); + value = value.copyWith(exposureMode: mode); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + /// Sets the exposure point for automatically determining the exposure value. + /// + /// Supplying a `null` value will reset the exposure point to it's default + /// value. + Future setExposurePoint(Offset? point) async { + if (point != null && + (point.dx < 0 || point.dx > 1 || point.dy < 0 || point.dy > 1)) { + throw ArgumentError( + 'The values of point should be anywhere between (0,0) and (1,1).'); + } + + try { + await CameraPlatform.instance.setExposurePoint( + _cameraId, + point == null + ? null + : Point( + point.dx, + point.dy, + ), + ); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + /// Gets the minimum supported exposure offset for the selected camera in EV units. + Future getMinExposureOffset() async { + _throwIfNotInitialized('getMinExposureOffset'); + try { + return CameraPlatform.instance.getMinExposureOffset(_cameraId); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + /// Gets the maximum supported exposure offset for the selected camera in EV units. + Future getMaxExposureOffset() async { + _throwIfNotInitialized('getMaxExposureOffset'); + try { + return CameraPlatform.instance.getMaxExposureOffset(_cameraId); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + /// Gets the supported step size for exposure offset for the selected camera in EV units. + /// + /// Returns 0 when the camera supports using a free value without stepping. + Future getExposureOffsetStepSize() async { + _throwIfNotInitialized('getExposureOffsetStepSize'); + try { + return CameraPlatform.instance.getExposureOffsetStepSize(_cameraId); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + /// Sets the exposure offset for the selected camera. + /// + /// The supplied [offset] value should be in EV units. 1 EV unit represents a + /// doubling in brightness. It should be between the minimum and maximum offsets + /// obtained through `getMinExposureOffset` and `getMaxExposureOffset` respectively. + /// Throws a `CameraException` when an illegal offset is supplied. + /// + /// When the supplied [offset] value does not align with the step size obtained + /// through `getExposureStepSize`, it will automatically be rounded to the nearest step. + /// + /// Returns the (rounded) offset value that was set. + Future setExposureOffset(double offset) async { + _throwIfNotInitialized('setExposureOffset'); + // Check if offset is in range + final List range = await Future.wait( + >[getMinExposureOffset(), getMaxExposureOffset()]); + if (offset < range[0] || offset > range[1]) { + throw CameraException( + 'exposureOffsetOutOfBounds', + 'The provided exposure offset was outside the supported range for this device.', + ); + } + + // Round to the closest step if needed + final double stepSize = await getExposureOffsetStepSize(); + if (stepSize > 0) { + final double inv = 1.0 / stepSize; + double roundedOffset = (offset * inv).roundToDouble() / inv; + if (roundedOffset > range[1]) { + roundedOffset = (offset * inv).floorToDouble() / inv; + } else if (roundedOffset < range[0]) { + roundedOffset = (offset * inv).ceilToDouble() / inv; + } + offset = roundedOffset; + } + + try { + return CameraPlatform.instance.setExposureOffset(_cameraId, offset); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + /// Locks the capture orientation. + /// + /// If [orientation] is omitted, the current device orientation is used. + Future lockCaptureOrientation([DeviceOrientation? orientation]) async { + try { + await CameraPlatform.instance.lockCaptureOrientation( + _cameraId, orientation ?? value.deviceOrientation); + value = value.copyWith( + lockedCaptureOrientation: Optional.of( + orientation ?? value.deviceOrientation)); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + /// Sets the focus mode for taking pictures. + Future setFocusMode(FocusMode mode) async { + try { + await CameraPlatform.instance.setFocusMode(_cameraId, mode); + value = value.copyWith(focusMode: mode); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + /// Unlocks the capture orientation. + Future unlockCaptureOrientation() async { + try { + await CameraPlatform.instance.unlockCaptureOrientation(_cameraId); + value = value.copyWith( + lockedCaptureOrientation: const Optional.absent()); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + /// Sets the focus point for automatically determining the focus value. + /// + /// Supplying a `null` value will reset the focus point to it's default + /// value. + Future setFocusPoint(Offset? point) async { + if (point != null && + (point.dx < 0 || point.dx > 1 || point.dy < 0 || point.dy > 1)) { + throw ArgumentError( + 'The values of point should be anywhere between (0,0) and (1,1).'); + } + try { + await CameraPlatform.instance.setFocusPoint( + _cameraId, + point == null + ? null + : Point( + point.dx, + point.dy, + ), + ); + } on PlatformException catch (e) { + throw CameraException(e.code, e.message); + } + } + + /// Releases the resources of this camera. + @override + Future dispose() async { + if (_isDisposed) { + return; + } + _unawaited(_deviceOrientationSubscription?.cancel()); + _isDisposed = true; + super.dispose(); + if (_initCalled != null) { + await _initCalled; + await CameraPlatform.instance.dispose(_cameraId); + } + } + + void _throwIfNotInitialized(String functionName) { + if (!value.isInitialized) { + throw CameraException( + 'Uninitialized CameraController', + '$functionName() was called on an uninitialized CameraController.', + ); + } + if (_isDisposed) { + throw CameraException( + 'Disposed CameraController', + '$functionName() was called on a disposed CameraController.', + ); + } + } + + @override + void removeListener(VoidCallback listener) { + // Prevent ValueListenableBuilder in CameraPreview widget from causing an + // exception to be thrown by attempting to remove its own listener after + // the controller has already been disposed. + if (!_isDisposed) { + super.removeListener(listener); + } + } +} + +/// A value that might be absent. +/// +/// Used to represent [DeviceOrientation]s that are optional but also able +/// to be cleared. +@immutable +class Optional extends IterableBase { + /// Constructs an empty Optional. + const Optional.absent() : _value = null; + + /// Constructs an Optional of the given [value]. + /// + /// Throws [ArgumentError] if [value] is null. + Optional.of(T value) : _value = value { + // TODO(cbracken): Delete and make this ctor const once mixed-mode + // execution is no longer around. + ArgumentError.checkNotNull(value); + } + + /// Constructs an Optional of the given [value]. + /// + /// If [value] is null, returns [absent()]. + const Optional.fromNullable(T? value) : _value = value; + + final T? _value; + + /// True when this optional contains a value. + bool get isPresent => _value != null; + + /// True when this optional contains no value. + bool get isNotPresent => _value == null; + + /// Gets the Optional value. + /// + /// Throws [StateError] if [value] is null. + T get value { + if (_value == null) { + throw StateError('value called on absent Optional.'); + } + return _value!; + } + + /// Executes a function if the Optional value is present. + void ifPresent(void Function(T value) ifPresent) { + if (isPresent) { + ifPresent(_value as T); + } + } + + /// Execution a function if the Optional value is absent. + void ifAbsent(void Function() ifAbsent) { + if (!isPresent) { + ifAbsent(); + } + } + + /// Gets the Optional value with a default. + /// + /// The default is returned if the Optional is [absent()]. + /// + /// Throws [ArgumentError] if [defaultValue] is null. + T or(T defaultValue) { + return _value ?? defaultValue; + } + + /// Gets the Optional value, or `null` if there is none. + T? get orNull => _value; + + /// Transforms the Optional value. + /// + /// If the Optional is [absent()], returns [absent()] without applying the transformer. + /// + /// The transformer must not return `null`. If it does, an [ArgumentError] is thrown. + Optional transform(S Function(T value) transformer) { + return _value == null + ? Optional.absent() + : Optional.of(transformer(_value as T)); + } + + /// Transforms the Optional value. + /// + /// If the Optional is [absent()], returns [absent()] without applying the transformer. + /// + /// Returns [absent()] if the transformer returns `null`. + Optional transformNullable(S? Function(T value) transformer) { + return _value == null + ? Optional.absent() + : Optional.fromNullable(transformer(_value as T)); + } + + @override + Iterator get iterator => + isPresent ? [_value as T].iterator : Iterable.empty().iterator; + + /// Delegates to the underlying [value] hashCode. + @override + int get hashCode => _value.hashCode; + + /// Delegates to the underlying [value] operator==. + @override + bool operator ==(Object o) => o is Optional && o._value == _value; + + @override + String toString() { + return _value == null + ? 'Optional { absent }' + : 'Optional { value: $_value }'; + } +} diff --git a/packages/camera/camera_android_camerax/example/lib/camera_image.dart b/packages/camera/camera_android_camerax/example/lib/camera_image.dart new file mode 100644 index 000000000000..bfcad6626dd6 --- /dev/null +++ b/packages/camera/camera_android_camerax/example/lib/camera_image.dart @@ -0,0 +1,177 @@ +// Copyright 2013 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. + +// TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#104231) +// ignore: unnecessary_import +import 'dart:typed_data'; + +import 'package:camera_platform_interface/camera_platform_interface.dart'; +import 'package:flutter/foundation.dart'; + +// TODO(stuartmorgan): Remove all of these classes in a breaking change, and +// vend the platform interface versions directly. See +// https://github.com/flutter/flutter/issues/104188 + +/// A single color plane of image data. +/// +/// The number and meaning of the planes in an image are determined by the +/// format of the Image. +class Plane { + Plane._fromPlatformInterface(CameraImagePlane plane) + : bytes = plane.bytes, + bytesPerPixel = plane.bytesPerPixel, + bytesPerRow = plane.bytesPerRow, + height = plane.height, + width = plane.width; + + // Only used by the deprecated codepath that's kept to avoid breaking changes. + // Never called by the plugin itself. + Plane._fromPlatformData(Map data) + : bytes = data['bytes'] as Uint8List, + bytesPerPixel = data['bytesPerPixel'] as int?, + bytesPerRow = data['bytesPerRow'] as int, + height = data['height'] as int?, + width = data['width'] as int?; + + /// Bytes representing this plane. + final Uint8List bytes; + + /// The distance between adjacent pixel samples on Android, in bytes. + /// + /// Will be `null` on iOS. + final int? bytesPerPixel; + + /// The row stride for this color plane, in bytes. + final int bytesPerRow; + + /// Height of the pixel buffer on iOS. + /// + /// Will be `null` on Android + final int? height; + + /// Width of the pixel buffer on iOS. + /// + /// Will be `null` on Android. + final int? width; +} + +/// Describes how pixels are represented in an image. +class ImageFormat { + ImageFormat._fromPlatformInterface(CameraImageFormat format) + : group = format.group, + raw = format.raw; + + // Only used by the deprecated codepath that's kept to avoid breaking changes. + // Never called by the plugin itself. + ImageFormat._fromPlatformData(this.raw) : group = _asImageFormatGroup(raw); + + /// Describes the format group the raw image format falls into. + final ImageFormatGroup group; + + /// Raw version of the format from the Android or iOS platform. + /// + /// On Android, this is an `int` from class `android.graphics.ImageFormat`. See + /// https://developer.android.com/reference/android/graphics/ImageFormat + /// + /// On iOS, this is a `FourCharCode` constant from Pixel Format Identifiers. + /// See https://developer.apple.com/documentation/corevideo/1563591-pixel_format_identifiers?language=objc + final dynamic raw; +} + +// Only used by the deprecated codepath that's kept to avoid breaking changes. +// Never called by the plugin itself. +ImageFormatGroup _asImageFormatGroup(dynamic rawFormat) { + if (defaultTargetPlatform == TargetPlatform.android) { + switch (rawFormat) { + // android.graphics.ImageFormat.YUV_420_888 + case 35: + return ImageFormatGroup.yuv420; + // android.graphics.ImageFormat.JPEG + case 256: + return ImageFormatGroup.jpeg; + } + } + + if (defaultTargetPlatform == TargetPlatform.iOS) { + switch (rawFormat) { + // kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange + case 875704438: + return ImageFormatGroup.yuv420; + // kCVPixelFormatType_32BGRA + case 1111970369: + return ImageFormatGroup.bgra8888; + } + } + + return ImageFormatGroup.unknown; +} + +/// A single complete image buffer from the platform camera. +/// +/// This class allows for direct application access to the pixel data of an +/// Image through one or more [Uint8List]. Each buffer is encapsulated in a +/// [Plane] that describes the layout of the pixel data in that plane. The +/// [CameraImage] is not directly usable as a UI resource. +/// +/// Although not all image formats are planar on iOS, we treat 1-dimensional +/// images as single planar images. +class CameraImage { + /// Creates a [CameraImage] from the platform interface version. + CameraImage.fromPlatformInterface(CameraImageData data) + : format = ImageFormat._fromPlatformInterface(data.format), + height = data.height, + width = data.width, + planes = List.unmodifiable(data.planes.map( + (CameraImagePlane plane) => Plane._fromPlatformInterface(plane))), + lensAperture = data.lensAperture, + sensorExposureTime = data.sensorExposureTime, + sensorSensitivity = data.sensorSensitivity; + + /// Creates a [CameraImage] from method channel data. + @Deprecated('Use fromPlatformInterface instead') + CameraImage.fromPlatformData(Map data) + : format = ImageFormat._fromPlatformData(data['format']), + height = data['height'] as int, + width = data['width'] as int, + lensAperture = data['lensAperture'] as double?, + sensorExposureTime = data['sensorExposureTime'] as int?, + sensorSensitivity = data['sensorSensitivity'] as double?, + planes = List.unmodifiable((data['planes'] as List) + .map((dynamic planeData) => + Plane._fromPlatformData(planeData as Map))); + + /// Format of the image provided. + /// + /// Determines the number of planes needed to represent the image, and + /// the general layout of the pixel data in each [Uint8List]. + final ImageFormat format; + + /// Height of the image in pixels. + /// + /// For formats where some color channels are subsampled, this is the height + /// of the largest-resolution plane. + final int height; + + /// Width of the image in pixels. + /// + /// For formats where some color channels are subsampled, this is the width + /// of the largest-resolution plane. + final int width; + + /// The pixels planes for this image. + /// + /// The number of planes is determined by the format of the image. + final List planes; + + /// The aperture settings for this image. + /// + /// Represented as an f-stop value. + final double? lensAperture; + + /// The sensor exposure time for this image in nanoseconds. + final int? sensorExposureTime; + + /// The sensor sensitivity in standard ISO arithmetic units. + final double? sensorSensitivity; +} diff --git a/packages/camera/camera_android_camerax/example/lib/camera_preview.dart b/packages/camera/camera_android_camerax/example/lib/camera_preview.dart new file mode 100644 index 000000000000..3baaaf8b1fa1 --- /dev/null +++ b/packages/camera/camera_android_camerax/example/lib/camera_preview.dart @@ -0,0 +1,81 @@ +// Copyright 2013 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. + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +import 'camera_controller.dart'; + +/// A widget showing a live camera preview. +class CameraPreview extends StatelessWidget { + /// Creates a preview widget for the given camera controller. + const CameraPreview(this.controller, {super.key, this.child}); + + /// The controller for the camera that the preview is shown for. + final CameraController controller; + + /// A widget to overlay on top of the camera preview + final Widget? child; + + @override + Widget build(BuildContext context) { + return controller.value.isInitialized + ? ValueListenableBuilder( + valueListenable: controller, + builder: (BuildContext context, Object? value, Widget? child) { + return AspectRatio( + aspectRatio: _isLandscape() + ? controller.value.aspectRatio + : (1 / controller.value.aspectRatio), + child: Stack( + fit: StackFit.expand, + children: [ + _wrapInRotatedBox(child: controller.buildPreview()), + child ?? Container(), + ], + ), + ); + }, + child: child, + ) + : Container(); + } + + Widget _wrapInRotatedBox({required Widget child}) { + if (kIsWeb || defaultTargetPlatform != TargetPlatform.android) { + return child; + } + + return RotatedBox( + quarterTurns: _getQuarterTurns(), + child: child, + ); + } + + bool _isLandscape() { + return [ + DeviceOrientation.landscapeLeft, + DeviceOrientation.landscapeRight + ].contains(_getApplicableOrientation()); + } + + int _getQuarterTurns() { + final Map turns = { + DeviceOrientation.portraitUp: 0, + DeviceOrientation.landscapeRight: 1, + DeviceOrientation.portraitDown: 2, + DeviceOrientation.landscapeLeft: 3, + }; + return turns[_getApplicableOrientation()]!; + } + + DeviceOrientation _getApplicableOrientation() { + return controller.value.isRecordingVideo + ? controller.value.recordingOrientation! + : (controller.value.previewPauseOrientation ?? + controller.value.lockedCaptureOrientation ?? + controller.value.deviceOrientation); + } +} diff --git a/packages/camera/camera_android_camerax/example/lib/main.dart b/packages/camera/camera_android_camerax/example/lib/main.dart index 244a15281e3f..4fd965271baa 100644 --- a/packages/camera/camera_android_camerax/example/lib/main.dart +++ b/packages/camera/camera_android_camerax/example/lib/main.dart @@ -2,43 +2,1046 @@ // 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:camera_platform_interface/camera_platform_interface.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:video_player/video_player.dart'; -late List _cameras; +import 'camera_controller.dart'; +import 'camera_preview.dart'; -Future main() async { - WidgetsFlutterBinding.ensureInitialized(); - _cameras = await CameraPlatform.instance.availableCameras(); +/// Camera example home widget. +class CameraExampleHome extends StatefulWidget { + /// Default Constructor + const CameraExampleHome({super.key}); - runApp(const MyApp()); + @override + State createState() { + return _CameraExampleHomeState(); + } } -/// Example app -class MyApp extends StatefulWidget { - /// App instantiation - const MyApp({super.key}); - @override - State createState() => _MyAppState(); +/// Returns a suitable camera icon for [direction]. +IconData getCameraLensIcon(CameraLensDirection direction) { + switch (direction) { + case CameraLensDirection.back: + return Icons.camera_rear; + case CameraLensDirection.front: + return Icons.camera_front; + case CameraLensDirection.external: + return Icons.camera; + } + // This enum is from a different package, so a new value could be added at + // any time. The example should keep working if that happens. + // ignore: dead_code + return Icons.camera; +} + +void _logError(String code, String? message) { + // ignore: avoid_print + print('Error: $code${message == null ? '' : '\nError Message: $message'}'); } -class _MyAppState extends State { +class _CameraExampleHomeState extends State + with WidgetsBindingObserver, TickerProviderStateMixin { + CameraController? controller; + XFile? imageFile; + XFile? videoFile; + VideoPlayerController? videoController; + VoidCallback? videoPlayerListener; + bool enableAudio = true; + double _minAvailableExposureOffset = 0.0; + double _maxAvailableExposureOffset = 0.0; + double _currentExposureOffset = 0.0; + late AnimationController _flashModeControlRowAnimationController; + late Animation _flashModeControlRowAnimation; + late AnimationController _exposureModeControlRowAnimationController; + late Animation _exposureModeControlRowAnimation; + late AnimationController _focusModeControlRowAnimationController; + late Animation _focusModeControlRowAnimation; + double _minAvailableZoom = 1.0; + double _maxAvailableZoom = 1.0; + double _currentScale = 1.0; + double _baseScale = 1.0; + + // Counting pointers (number of user fingers on screen) + int _pointers = 0; + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addObserver(this); + + _flashModeControlRowAnimationController = AnimationController( + duration: const Duration(milliseconds: 300), + vsync: this, + ); + _flashModeControlRowAnimation = CurvedAnimation( + parent: _flashModeControlRowAnimationController, + curve: Curves.easeInCubic, + ); + _exposureModeControlRowAnimationController = AnimationController( + duration: const Duration(milliseconds: 300), + vsync: this, + ); + _exposureModeControlRowAnimation = CurvedAnimation( + parent: _exposureModeControlRowAnimationController, + curve: Curves.easeInCubic, + ); + _focusModeControlRowAnimationController = AnimationController( + duration: const Duration(milliseconds: 300), + vsync: this, + ); + _focusModeControlRowAnimation = CurvedAnimation( + parent: _focusModeControlRowAnimationController, + curve: Curves.easeInCubic, + ); + } + + @override + void dispose() { + WidgetsBinding.instance.removeObserver(this); + _flashModeControlRowAnimationController.dispose(); + _exposureModeControlRowAnimationController.dispose(); + super.dispose(); + } + + // #docregion AppLifecycle + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + final CameraController? cameraController = controller; + + // App state changed before we got the chance to initialize. + if (cameraController == null || !cameraController.value.isInitialized) { + return; + } + + if (state == AppLifecycleState.inactive) { + cameraController.dispose(); + } else if (state == AppLifecycleState.resumed) { + onNewCameraSelected(cameraController.description); + } + } + // #enddocregion AppLifecycle + @override Widget build(BuildContext context) { - String availableCameraNames = 'Available cameras:'; - for (final CameraDescription cameraDescription in _cameras) { - availableCameraNames = '$availableCameraNames ${cameraDescription.name},'; + return Scaffold( + appBar: AppBar( + title: const Text('Camera example'), + ), + body: Column( + children: [ + Expanded( + child: Container( + decoration: BoxDecoration( + color: Colors.black, + border: Border.all( + color: + controller != null && controller!.value.isRecordingVideo + ? Colors.redAccent + : Colors.grey, + width: 3.0, + ), + ), + child: Padding( + padding: const EdgeInsets.all(1.0), + child: Center( + child: _cameraPreviewWidget(), + ), + ), + ), + ), + _captureControlRowWidget(), + _modeControlRowWidget(), + Padding( + padding: const EdgeInsets.all(5.0), + child: Row( + children: [ + _cameraTogglesRowWidget(), + _thumbnailWidget(), + ], + ), + ), + ], + ), + ); + } + + /// Display the preview from the camera (or a message if the preview is not available). + Widget _cameraPreviewWidget() { + final CameraController? cameraController = controller; + + if (cameraController == null || !cameraController.value.isInitialized) { + return const Text( + 'Tap a camera', + style: TextStyle( + color: Colors.white, + fontSize: 24.0, + fontWeight: FontWeight.w900, + ), + ); + } else { + return Listener( + onPointerDown: (_) => _pointers++, + onPointerUp: (_) => _pointers--, + child: CameraPreview( + controller!, + child: LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + return GestureDetector( + behavior: HitTestBehavior.opaque, + onScaleStart: _handleScaleStart, + onScaleUpdate: _handleScaleUpdate, + onTapDown: (TapDownDetails details) => + onViewFinderTap(details, constraints), + ); + }), + ), + ); + } + } + + void _handleScaleStart(ScaleStartDetails details) { + _baseScale = _currentScale; + } + + Future _handleScaleUpdate(ScaleUpdateDetails details) async { + // When there are not exactly two fingers on screen don't scale + if (controller == null || _pointers != 2) { + return; } - return MaterialApp( - home: Scaffold( - appBar: AppBar( - title: const Text('Camera Example'), + + _currentScale = (_baseScale * details.scale) + .clamp(_minAvailableZoom, _maxAvailableZoom); + + await controller!.setZoomLevel(_currentScale); + } + + /// Display the thumbnail of the captured image or video. + Widget _thumbnailWidget() { + final VideoPlayerController? localVideoController = videoController; + + return Expanded( + child: Align( + alignment: Alignment.centerRight, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (localVideoController == null && imageFile == null) + Container() + else + SizedBox( + width: 64.0, + height: 64.0, + child: (localVideoController == null) + ? ( + // The captured image on the web contains a network-accessible URL + // pointing to a location within the browser. It may be displayed + // either with Image.network or Image.memory after loading the image + // bytes to memory. + kIsWeb + ? Image.network(imageFile!.path) + : Image.file(File(imageFile!.path))) + : Container( + decoration: BoxDecoration( + border: Border.all(color: Colors.pink)), + child: Center( + child: AspectRatio( + aspectRatio: + localVideoController.value.size != null + ? localVideoController.value.aspectRatio + : 1.0, + child: VideoPlayer(localVideoController)), + ), + ), + ), + ], + ), + ), + ); + } + + /// Display a bar with buttons to change the flash and exposure modes + Widget _modeControlRowWidget() { + return Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + IconButton( + icon: const Icon(Icons.flash_on), + color: Colors.blue, + onPressed: () {}, // TODO(camsim99): Add functionality back here. + ), + // The exposure and focus mode are currently not supported on the web. + ...!kIsWeb + ? [ + IconButton( + icon: const Icon(Icons.exposure), + color: Colors.blue, + onPressed: + () {}, // TODO(camsim99): Add functionality back here. + ), + IconButton( + icon: const Icon(Icons.filter_center_focus), + color: Colors.blue, + onPressed: + () {}, // TODO(camsim99): Add functionality back here. + ) + ] + : [], + IconButton( + icon: Icon(enableAudio ? Icons.volume_up : Icons.volume_mute), + color: Colors.blue, + onPressed: () {}, // TODO(camsim99): Add functionality back here. + ), + IconButton( + icon: Icon(controller?.value.isCaptureOrientationLocked ?? false + ? Icons.screen_lock_rotation + : Icons.screen_rotation), + color: Colors.blue, + onPressed: () {}, // TODO(camsim99): Add functionality back here. + ), + ], ), - body: Center( - child: Text(availableCameraNames.substring( - 0, availableCameraNames.length - 1)), + _flashModeControlRowWidget(), + _exposureModeControlRowWidget(), + _focusModeControlRowWidget(), + ], + ); + } + + Widget _flashModeControlRowWidget() { + return SizeTransition( + sizeFactor: _flashModeControlRowAnimation, + child: ClipRect( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + IconButton( + icon: const Icon(Icons.flash_off), + color: controller?.value.flashMode == FlashMode.off + ? Colors.orange + : Colors.blue, + onPressed: () {}, // TODO(camsim99): Add functionality back here. + ), + IconButton( + icon: const Icon(Icons.flash_auto), + color: controller?.value.flashMode == FlashMode.auto + ? Colors.orange + : Colors.blue, + onPressed: () {}, // TODO(camsim99): Add functionality back here. + ), + IconButton( + icon: const Icon(Icons.flash_on), + color: controller?.value.flashMode == FlashMode.always + ? Colors.orange + : Colors.blue, + onPressed: () {}, // TODO(camsim99): Add functionality back here. + ), + IconButton( + icon: const Icon(Icons.highlight), + color: controller?.value.flashMode == FlashMode.torch + ? Colors.orange + : Colors.blue, + onPressed: () {}, // TODO(camsim99): Add functionality back here. + ), + ], ), ), ); } + + Widget _exposureModeControlRowWidget() { + final ButtonStyle styleAuto = TextButton.styleFrom( + // TODO(darrenaustin): Migrate to new API once it lands in stable: https://github.com/flutter/flutter/issues/105724 + // ignore: deprecated_member_use + primary: controller?.value.exposureMode == ExposureMode.auto + ? Colors.orange + : Colors.blue, + ); + final ButtonStyle styleLocked = TextButton.styleFrom( + // TODO(darrenaustin): Migrate to new API once it lands in stable: https://github.com/flutter/flutter/issues/105724 + // ignore: deprecated_member_use + primary: controller?.value.exposureMode == ExposureMode.locked + ? Colors.orange + : Colors.blue, + ); + + return SizeTransition( + sizeFactor: _exposureModeControlRowAnimation, + child: ClipRect( + child: Container( + color: Colors.grey.shade50, + child: Column( + children: [ + const Center( + child: Text('Exposure Mode'), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + TextButton( + style: styleAuto, + onPressed: + () {}, // TODO(camsim99): Add functionality back here. + onLongPress: () { + if (controller != null) { + controller!.setExposurePoint(null); + showInSnackBar('Resetting exposure point'); + } + }, + child: const Text('AUTO'), + ), + TextButton( + style: styleLocked, + onPressed: + () {}, // TODO(camsim99): Add functionality back here. + child: const Text('LOCKED'), + ), + TextButton( + style: styleLocked, + onPressed: + () {}, // TODO(camsim99): Add functionality back here. + child: const Text('RESET OFFSET'), + ), + ], + ), + const Center( + child: Text('Exposure Offset'), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Text(_minAvailableExposureOffset.toString()), + Slider( + value: _currentExposureOffset, + min: _minAvailableExposureOffset, + max: _maxAvailableExposureOffset, + label: _currentExposureOffset.toString(), + onChanged: _minAvailableExposureOffset == + _maxAvailableExposureOffset + ? null + : setExposureOffset, + ), + Text(_maxAvailableExposureOffset.toString()), + ], + ), + ], + ), + ), + ), + ); + } + + Widget _focusModeControlRowWidget() { + final ButtonStyle styleAuto = TextButton.styleFrom( + // TODO(darrenaustin): Migrate to new API once it lands in stable: https://github.com/flutter/flutter/issues/105724 + // ignore: deprecated_member_use + primary: controller?.value.focusMode == FocusMode.auto + ? Colors.orange + : Colors.blue, + ); + final ButtonStyle styleLocked = TextButton.styleFrom( + // TODO(darrenaustin): Migrate to new API once it lands in stable: https://github.com/flutter/flutter/issues/105724 + // ignore: deprecated_member_use + primary: controller?.value.focusMode == FocusMode.locked + ? Colors.orange + : Colors.blue, + ); + + return SizeTransition( + sizeFactor: _focusModeControlRowAnimation, + child: ClipRect( + child: Container( + color: Colors.grey.shade50, + child: Column( + children: [ + const Center( + child: Text('Focus Mode'), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + TextButton( + style: styleAuto, + onPressed: + () {}, // TODO(camsim99): Add functionality back here. + onLongPress: () { + if (controller != null) { + controller!.setFocusPoint(null); + } + showInSnackBar('Resetting focus point'); + }, + child: const Text('AUTO'), + ), + TextButton( + style: styleLocked, + onPressed: + () {}, // TODO(camsim99): Add functionality back here. + child: const Text('LOCKED'), + ), + ], + ), + ], + ), + ), + ), + ); + } + + /// Display the control bar with buttons to take pictures and record videos. + Widget _captureControlRowWidget() { + final CameraController? cameraController = controller; + + return Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + IconButton( + icon: const Icon(Icons.camera_alt), + color: Colors.blue, + onPressed: () {}, // TODO(camsim99): Add functionality back here. + ), + IconButton( + icon: const Icon(Icons.videocam), + color: Colors.blue, + onPressed: () {}, // TODO(camsim99): Add functionality back here. + ), + IconButton( + icon: cameraController != null && + cameraController.value.isRecordingPaused + ? const Icon(Icons.play_arrow) + : const Icon(Icons.pause), + color: Colors.blue, + onPressed: () {}, // TODO(camsim99): Add functionality back here. + ), + IconButton( + icon: const Icon(Icons.stop), + color: Colors.red, + onPressed: () {}, // TODO(camsim99): Add functionality back here. + ), + IconButton( + icon: const Icon(Icons.pause_presentation), + color: + cameraController != null && cameraController.value.isPreviewPaused + ? Colors.red + : Colors.blue, + onPressed: + cameraController == null ? null : onPausePreviewButtonPressed, + ), + ], + ); + } + + /// Display a row of toggle to select the camera (or a message if no camera is available). + Widget _cameraTogglesRowWidget() { + final List toggles = []; + + void onChanged(CameraDescription? description) { + if (description == null) { + return; + } + + onNewCameraSelected(description); + } + + if (_cameras.isEmpty) { + SchedulerBinding.instance.addPostFrameCallback((_) async { + showInSnackBar('No camera found.'); + }); + return const Text('None'); + } else { + for (final CameraDescription cameraDescription in _cameras) { + toggles.add( + SizedBox( + width: 90.0, + child: RadioListTile( + title: Icon(getCameraLensIcon(cameraDescription.lensDirection)), + groupValue: controller?.description, + value: cameraDescription, + onChanged: + controller != null && controller!.value.isRecordingVideo + ? null + : onChanged, + ), + ), + ); + } + } + + return Row(children: toggles); + } + + String timestamp() => DateTime.now().millisecondsSinceEpoch.toString(); + + void showInSnackBar(String message) { + ScaffoldMessenger.of(context) + .showSnackBar(SnackBar(content: Text(message))); + } + + void onViewFinderTap(TapDownDetails details, BoxConstraints constraints) { + if (controller == null) { + return; + } + + final CameraController cameraController = controller!; + + final Offset offset = Offset( + details.localPosition.dx / constraints.maxWidth, + details.localPosition.dy / constraints.maxHeight, + ); + cameraController.setExposurePoint(offset); + cameraController.setFocusPoint(offset); + } + + Future onNewCameraSelected(CameraDescription cameraDescription) async { + final CameraController? oldController = controller; + if (oldController != null) { + // `controller` needs to be set to null before getting disposed, + // to avoid a race condition when we use the controller that is being + // disposed. This happens when camera permission dialog shows up, + // which triggers `didChangeAppLifecycleState`, which disposes and + // re-creates the controller. + controller = null; + await oldController.dispose(); + } + + final CameraController cameraController = CameraController( + cameraDescription, + kIsWeb ? ResolutionPreset.max : ResolutionPreset.medium, + enableAudio: enableAudio, + imageFormatGroup: ImageFormatGroup.jpeg, + ); + + controller = cameraController; + + // If the controller is updated then update the UI. + cameraController.addListener(() { + if (mounted) { + setState(() {}); + } + if (cameraController.value.hasError) { + showInSnackBar( + 'Camera error ${cameraController.value.errorDescription}'); + } + }); + + try { + await cameraController.initialize(); + await Future.wait(>[ + // The exposure mode is currently not supported on the web. + ...!kIsWeb + ? >[ + cameraController.getMinExposureOffset().then( + (double value) => _minAvailableExposureOffset = value), + cameraController + .getMaxExposureOffset() + .then((double value) => _maxAvailableExposureOffset = value) + ] + : >[], + cameraController + .getMaxZoomLevel() + .then((double value) => _maxAvailableZoom = value), + cameraController + .getMinZoomLevel() + .then((double value) => _minAvailableZoom = value), + ]); + } on CameraException catch (e) { + switch (e.code) { + case 'CameraAccessDenied': + showInSnackBar('You have denied camera access.'); + break; + case 'CameraAccessDeniedWithoutPrompt': + // iOS only + showInSnackBar('Please go to Settings app to enable camera access.'); + break; + case 'CameraAccessRestricted': + // iOS only + showInSnackBar('Camera access is restricted.'); + break; + case 'AudioAccessDenied': + showInSnackBar('You have denied audio access.'); + break; + case 'AudioAccessDeniedWithoutPrompt': + // iOS only + showInSnackBar('Please go to Settings app to enable audio access.'); + break; + case 'AudioAccessRestricted': + // iOS only + showInSnackBar('Audio access is restricted.'); + break; + default: + _showCameraException(e); + break; + } + } + + if (mounted) { + setState(() {}); + } + } + + void onTakePictureButtonPressed() { + takePicture().then((XFile? file) { + if (mounted) { + setState(() { + imageFile = file; + videoController?.dispose(); + videoController = null; + }); + if (file != null) { + showInSnackBar('Picture saved to ${file.path}'); + } + } + }); + } + + void onFlashModeButtonPressed() { + if (_flashModeControlRowAnimationController.value == 1) { + _flashModeControlRowAnimationController.reverse(); + } else { + _flashModeControlRowAnimationController.forward(); + _exposureModeControlRowAnimationController.reverse(); + _focusModeControlRowAnimationController.reverse(); + } + } + + void onExposureModeButtonPressed() { + if (_exposureModeControlRowAnimationController.value == 1) { + _exposureModeControlRowAnimationController.reverse(); + } else { + _exposureModeControlRowAnimationController.forward(); + _flashModeControlRowAnimationController.reverse(); + _focusModeControlRowAnimationController.reverse(); + } + } + + void onFocusModeButtonPressed() { + if (_focusModeControlRowAnimationController.value == 1) { + _focusModeControlRowAnimationController.reverse(); + } else { + _focusModeControlRowAnimationController.forward(); + _flashModeControlRowAnimationController.reverse(); + _exposureModeControlRowAnimationController.reverse(); + } + } + + void onAudioModeButtonPressed() { + enableAudio = !enableAudio; + if (controller != null) { + onNewCameraSelected(controller!.description); + } + } + + Future onCaptureOrientationLockButtonPressed() async { + try { + if (controller != null) { + final CameraController cameraController = controller!; + if (cameraController.value.isCaptureOrientationLocked) { + await cameraController.unlockCaptureOrientation(); + showInSnackBar('Capture orientation unlocked'); + } else { + await cameraController.lockCaptureOrientation(); + showInSnackBar( + 'Capture orientation locked to ${cameraController.value.lockedCaptureOrientation.toString().split('.').last}'); + } + } + } on CameraException catch (e) { + _showCameraException(e); + } + } + + void onSetFlashModeButtonPressed(FlashMode mode) { + setFlashMode(mode).then((_) { + if (mounted) { + setState(() {}); + } + showInSnackBar('Flash mode set to ${mode.toString().split('.').last}'); + }); + } + + void onSetExposureModeButtonPressed(ExposureMode mode) { + setExposureMode(mode).then((_) { + if (mounted) { + setState(() {}); + } + showInSnackBar('Exposure mode set to ${mode.toString().split('.').last}'); + }); + } + + void onSetFocusModeButtonPressed(FocusMode mode) { + setFocusMode(mode).then((_) { + if (mounted) { + setState(() {}); + } + showInSnackBar('Focus mode set to ${mode.toString().split('.').last}'); + }); + } + + void onVideoRecordButtonPressed() { + startVideoRecording().then((_) { + if (mounted) { + setState(() {}); + } + }); + } + + void onStopButtonPressed() { + stopVideoRecording().then((XFile? file) { + if (mounted) { + setState(() {}); + } + if (file != null) { + showInSnackBar('Video recorded to ${file.path}'); + videoFile = file; + _startVideoPlayer(); + } + }); + } + + Future onPausePreviewButtonPressed() async { + final CameraController? cameraController = controller; + + if (cameraController == null || !cameraController.value.isInitialized) { + showInSnackBar('Error: select a camera first.'); + return; + } + + if (cameraController.value.isPreviewPaused) { + await cameraController.resumePreview(); + } else { + await cameraController.pausePreview(); + } + + if (mounted) { + setState(() {}); + } + } + + void onPauseButtonPressed() { + pauseVideoRecording().then((_) { + if (mounted) { + setState(() {}); + } + showInSnackBar('Video recording paused'); + }); + } + + void onResumeButtonPressed() { + resumeVideoRecording().then((_) { + if (mounted) { + setState(() {}); + } + showInSnackBar('Video recording resumed'); + }); + } + + Future startVideoRecording() async { + final CameraController? cameraController = controller; + + if (cameraController == null || !cameraController.value.isInitialized) { + showInSnackBar('Error: select a camera first.'); + return; + } + + if (cameraController.value.isRecordingVideo) { + // A recording is already started, do nothing. + return; + } + + try { + await cameraController.startVideoRecording(); + } on CameraException catch (e) { + _showCameraException(e); + return; + } + } + + Future stopVideoRecording() async { + final CameraController? cameraController = controller; + + if (cameraController == null || !cameraController.value.isRecordingVideo) { + return null; + } + + try { + return cameraController.stopVideoRecording(); + } on CameraException catch (e) { + _showCameraException(e); + return null; + } + } + + Future pauseVideoRecording() async { + final CameraController? cameraController = controller; + + if (cameraController == null || !cameraController.value.isRecordingVideo) { + return; + } + + try { + await cameraController.pauseVideoRecording(); + } on CameraException catch (e) { + _showCameraException(e); + rethrow; + } + } + + Future resumeVideoRecording() async { + final CameraController? cameraController = controller; + + if (cameraController == null || !cameraController.value.isRecordingVideo) { + return; + } + + try { + await cameraController.resumeVideoRecording(); + } on CameraException catch (e) { + _showCameraException(e); + rethrow; + } + } + + Future setFlashMode(FlashMode mode) async { + if (controller == null) { + return; + } + + try { + await controller!.setFlashMode(mode); + } on CameraException catch (e) { + _showCameraException(e); + rethrow; + } + } + + Future setExposureMode(ExposureMode mode) async { + if (controller == null) { + return; + } + + try { + await controller!.setExposureMode(mode); + } on CameraException catch (e) { + _showCameraException(e); + rethrow; + } + } + + Future setExposureOffset(double offset) async { + if (controller == null) { + return; + } + + setState(() { + _currentExposureOffset = offset; + }); + try { + offset = await controller!.setExposureOffset(offset); + } on CameraException catch (e) { + _showCameraException(e); + rethrow; + } + } + + Future setFocusMode(FocusMode mode) async { + if (controller == null) { + return; + } + + try { + await controller!.setFocusMode(mode); + } on CameraException catch (e) { + _showCameraException(e); + rethrow; + } + } + + Future _startVideoPlayer() async { + if (videoFile == null) { + return; + } + + final VideoPlayerController vController = kIsWeb + ? VideoPlayerController.network(videoFile!.path) + : VideoPlayerController.file(File(videoFile!.path)); + + videoPlayerListener = () { + if (videoController != null && videoController!.value.size != null) { + // Refreshing the state to update video player with the correct ratio. + if (mounted) { + setState(() {}); + } + videoController!.removeListener(videoPlayerListener!); + } + }; + vController.addListener(videoPlayerListener!); + await vController.setLooping(true); + await vController.initialize(); + await videoController?.dispose(); + if (mounted) { + setState(() { + imageFile = null; + videoController = vController; + }); + } + await vController.play(); + } + + Future takePicture() async { + final CameraController? cameraController = controller; + if (cameraController == null || !cameraController.value.isInitialized) { + showInSnackBar('Error: select a camera first.'); + return null; + } + + if (cameraController.value.isTakingPicture) { + // A capture is already pending, do nothing. + return null; + } + + try { + final XFile file = await cameraController.takePicture(); + return file; + } on CameraException catch (e) { + _showCameraException(e); + return null; + } + } + + void _showCameraException(CameraException e) { + _logError(e.code, e.description); + showInSnackBar('Error: ${e.code}\n${e.description}'); + } +} + +/// CameraApp is the Main Application. +class CameraApp extends StatelessWidget { + /// Default Constructor + const CameraApp({super.key}); + + @override + Widget build(BuildContext context) { + return const MaterialApp( + home: CameraExampleHome(), + ); + } +} + +List _cameras = []; + +Future main() async { + // Fetch the available cameras before initializing the app. + try { + WidgetsFlutterBinding.ensureInitialized(); + _cameras = await availableCameras(); + } on CameraException catch (e) { + _logError(e.code, e.description); + } + runApp(const CameraApp()); } diff --git a/packages/camera/camera_android_camerax/example/pubspec.yaml b/packages/camera/camera_android_camerax/example/pubspec.yaml index d9756f7ebd9b..49a29b8517d9 100644 --- a/packages/camera/camera_android_camerax/example/pubspec.yaml +++ b/packages/camera/camera_android_camerax/example/pubspec.yaml @@ -17,6 +17,7 @@ dependencies: camera_platform_interface: ^2.2.0 flutter: sdk: flutter + video_player: ^2.4.10 dev_dependencies: flutter_test: diff --git a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart index 300d6717fb46..18debf688547 100644 --- a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart +++ b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart @@ -5,11 +5,18 @@ import 'dart:async'; import 'package:camera_platform_interface/camera_platform_interface.dart'; -import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; +import 'package:stream_transform/stream_transform.dart'; +import 'camera.dart'; import 'camera_info.dart'; import 'camera_selector.dart'; +import 'camerax_library.g.dart'; +import 'preview.dart'; import 'process_camera_provider.dart'; +import 'surface.dart'; +import 'system_services.dart'; +import 'use_case.dart'; /// The Android implementation of [CameraPlatform] that uses the CameraX library. class AndroidCameraCameraX extends CameraPlatform { @@ -18,17 +25,45 @@ class AndroidCameraCameraX extends CameraPlatform { CameraPlatform.instance = AndroidCameraCameraX(); } - /// ProcessCameraProvider used to get list of cameras. Visible only for testing. + /// The [ProcessCameraProvider] instance used to access camera functionality. @visibleForTesting ProcessCameraProvider? processCameraProvider; - /// Camera selector used to determine which CameraInfos are back cameras. + /// The [Camera] instance returned by the [processCameraProvider] when a [UseCase] is + /// bound to the lifecycle of the camera it manages. @visibleForTesting - CameraSelector? backCameraSelector = CameraSelector.getDefaultBackCamera(); + Camera? camera; - /// Camera selector used to determine which CameraInfos are back cameras. + /// The [Preview] instance that can be configured to present a live camera preview. @visibleForTesting - CameraSelector? frontCameraSelector = CameraSelector.getDefaultFrontCamera(); + Preview? preview; + + /// Whether or not the [preview] is currently bound to the lifecycle that the + /// [processCameraProvider] tracks. + @visibleForTesting + bool previewIsBound = false; + + bool _previewIsPaused = false; + + /// The [CameraSelector] used to configure the [processCameraProvider] to use + /// the desired camera. + @visibleForTesting + CameraSelector? cameraSelector; + + /// The controller we need to broadcast the different camera events. + /// + /// It is a `broadcast` because multiple controllers will connect to + /// different stream views of this Controller. + /// This is only exposed for test purposes. It shouldn't be used by clients of + /// the plugin as it may break or change at any time. + @visibleForTesting + final StreamController cameraEventStreamController = + StreamController.broadcast(); + + /// The stream of camera events. + Stream _cameraEvents(int cameraId) => + cameraEventStreamController.stream + .where((CameraEvent event) => event.cameraId == cameraId); /// Returns list of all available cameras and their descriptions. @override @@ -47,10 +82,12 @@ class AndroidCameraCameraX extends CameraPlatform { for (final CameraInfo cameraInfo in cameraInfos) { // Determine the lens direction by filtering the CameraInfo // TODO(gmackall): replace this with call to CameraInfo.getLensFacing when changes containing that method are available - if ((await backCameraSelector!.filter([cameraInfo])) + if ((await createCameraSelector(CameraSelector.lensFacingBack) + .filter([cameraInfo])) .isNotEmpty) { cameraLensDirection = CameraLensDirection.back; - } else if ((await frontCameraSelector!.filter([cameraInfo])) + } else if ((await createCameraSelector(CameraSelector.lensFacingFront) + .filter([cameraInfo])) .isNotEmpty) { cameraLensDirection = CameraLensDirection.front; } else { @@ -70,4 +107,276 @@ class AndroidCameraCameraX extends CameraPlatform { return cameraDescriptions; } + + /// Creates an uninitialized camera instance and returns the camera ID. + /// + /// In the CameraX library, cameras are accessed by combining [UseCase]s + /// to an instance of a [ProcessCameraProvider]. Thus, to create an + /// unitialized camera instance, this method retrieves a + /// [ProcessCameraProvider] instance. + /// + /// To return the camera ID, which is equivalent to the ID of the surface texture + /// that a camera preview can be drawn to, a [Preview] instance is configured + /// and bound to the [ProcessCameraProvider] instance. + @override + Future createCamera( + CameraDescription cameraDescription, + ResolutionPreset? resolutionPreset, { + bool enableAudio = false, + }) async { + // Must obtain proper permissions before attempting to access a camera. + await requestCameraPermissions(enableAudio); + + // Save CameraSelector that matches cameraDescription. + final int cameraSelectorLensDirection = + _getCameraSelectorLensDirection(cameraDescription.lensDirection); + final bool cameraIsFrontFacing = + cameraSelectorLensDirection == CameraSelector.lensFacingFront; + cameraSelector = createCameraSelector(cameraSelectorLensDirection); + // Start listening for device orientation changes preceding camera creation. + startListeningForDeviceOrientationChange( + cameraIsFrontFacing, cameraDescription.sensorOrientation); + + // Retrieve a ProcessCameraProvider instance. + processCameraProvider ??= await ProcessCameraProvider.getInstance(); + + // Configure Preview instance and bind to ProcessCameraProvider. + final int targetRotation = + _getTargetRotation(cameraDescription.sensorOrientation); + final ResolutionInfo? targetResolution = + _getTargetResolutionForPreview(resolutionPreset); + preview = createPreview(targetRotation, targetResolution); + previewIsBound = false; + _previewIsPaused = false; + final int flutterSurfaceTextureId = await preview!.setSurfaceProvider(); + + return flutterSurfaceTextureId; + } + + /// Initializes the camera on the device. + /// + /// Since initialization of a camera does not directly map as an operation to + /// the CameraX library, this method just retrieves information about the + /// camera and sends a [CameraInitializedEvent]. + /// + /// [imageFormatGroup] is used to specify the image formatting used. + /// On Android this defaults to ImageFormat.YUV_420_888 and applies only to + /// the image stream. + @override + Future initializeCamera( + int cameraId, { + ImageFormatGroup imageFormatGroup = ImageFormatGroup.unknown, + }) async { + // TODO(camsim99): Use imageFormatGroup to configure ImageAnalysis use case + // for image streaming. + // https://github.com/flutter/flutter/issues/120463 + + // Configure CameraInitializedEvent to send as representation of a + // configured camera: + // Retrieve preview resolution. + assert( + preview != null, + 'Preview instance not found. Please call the "createCamera" method before calling "initializeCamera"', + ); + await _bindPreviewToLifecycle(); + final ResolutionInfo previewResolutionInfo = + await preview!.getResolutionInfo(); + _unbindPreviewFromLifecycle(); + + // Retrieve exposure and focus mode configurations: + // TODO(camsim99): Implement support for retrieving exposure mode configuration. + // https://github.com/flutter/flutter/issues/120468 + const ExposureMode exposureMode = ExposureMode.auto; + const bool exposurePointSupported = false; + + // TODO(camsim99): Implement support for retrieving focus mode configuration. + // https://github.com/flutter/flutter/issues/120467 + const FocusMode focusMode = FocusMode.auto; + const bool focusPointSupported = false; + + cameraEventStreamController.add(CameraInitializedEvent( + cameraId, + previewResolutionInfo.width.toDouble(), + previewResolutionInfo.height.toDouble(), + exposureMode, + exposurePointSupported, + focusMode, + focusPointSupported)); + } + + /// Releases the resources of the accessed camera. + /// + /// [cameraId] not used. + @override + Future dispose(int cameraId) async { + preview?.releaseFlutterSurfaceTexture(); + processCameraProvider?.unbindAll(); + } + + /// The camera has been initialized. + @override + Stream onCameraInitialized(int cameraId) { + return _cameraEvents(cameraId).whereType(); + } + + /// The camera experienced an error. + @override + Stream onCameraError(int cameraId) { + return SystemServices.cameraErrorStreamController.stream + .map((String errorDescription) { + return CameraErrorEvent(cameraId, errorDescription); + }); + } + + /// The ui orientation changed. + @override + Stream onDeviceOrientationChanged() { + return SystemServices.deviceOrientationChangedStreamController.stream; + } + + /// Pause the active preview on the current frame for the selected camera. + /// + /// [cameraId] not used. + @override + Future pausePreview(int cameraId) async { + _unbindPreviewFromLifecycle(); + _previewIsPaused = true; + } + + /// Resume the paused preview for the selected camera. + /// + /// [cameraId] not used. + @override + Future resumePreview(int cameraId) async { + await _bindPreviewToLifecycle(); + _previewIsPaused = false; + } + + /// Returns a widget showing a live camera preview. + @override + Widget buildPreview(int cameraId) { + return FutureBuilder( + future: _bindPreviewToLifecycle(), + builder: (BuildContext context, AsyncSnapshot snapshot) { + switch (snapshot.connectionState) { + case ConnectionState.none: + case ConnectionState.waiting: + case ConnectionState.active: + // Do nothing while waiting for preview to be bound to lifecyle. + return const SizedBox.shrink(); + case ConnectionState.done: + return Texture(textureId: cameraId); + } + }); + } + + // Methods for binding UseCases to the lifecycle of the camera controlled + // by a ProcessCameraProvider instance: + + /// Binds [preview] instance to the camera lifecycle controlled by the + /// [processCameraProvider]. + Future _bindPreviewToLifecycle() async { + assert(processCameraProvider != null); + assert(cameraSelector != null); + + if (previewIsBound || _previewIsPaused) { + // Only bind if preview is not already bound or intentionally paused. + return; + } + + camera = await processCameraProvider! + .bindToLifecycle(cameraSelector!, [preview!]); + previewIsBound = true; + } + + /// Unbinds [preview] instance to camera lifecycle controlled by the + /// [processCameraProvider]. + void _unbindPreviewFromLifecycle() { + if (preview == null || !previewIsBound) { + return; + } + + assert(processCameraProvider != null); + + processCameraProvider!.unbind([preview!]); + previewIsBound = false; + } + + // Methods for mapping Flutter camera constants to CameraX constants: + + /// Returns [CameraSelector] lens direction that maps to specified + /// [CameraLensDirection]. + int _getCameraSelectorLensDirection(CameraLensDirection lensDirection) { + switch (lensDirection) { + case CameraLensDirection.front: + return CameraSelector.lensFacingFront; + case CameraLensDirection.back: + return CameraSelector.lensFacingBack; + case CameraLensDirection.external: + return CameraSelector.lensFacingExternal; + } + } + + /// Returns [Surface] target rotation constant that maps to specified sensor + /// orientation. + int _getTargetRotation(int sensorOrientation) { + switch (sensorOrientation) { + case 90: + return Surface.ROTATION_90; + case 180: + return Surface.ROTATION_180; + case 270: + return Surface.ROTATION_270; + case 0: + return Surface.ROTATION_0; + default: + throw ArgumentError( + '"$sensorOrientation" is not a valid sensor orientation value'); + } + } + + /// Returns [ResolutionInfo] that maps to the specified resolution preset for + /// a camera preview. + ResolutionInfo? _getTargetResolutionForPreview(ResolutionPreset? resolution) { + // TODO(camsim99): Implement resolution configuration. + // https://github.com/flutter/flutter/issues/120462 + return null; + } + + // Methods for calls that need to be tested: + + /// Requests camera permissions. + @visibleForTesting + Future requestCameraPermissions(bool enableAudio) async { + await SystemServices.requestCameraPermissions(enableAudio); + } + + /// Subscribes the plugin as a listener to changes in device orientation. + @visibleForTesting + void startListeningForDeviceOrientationChange( + bool cameraIsFrontFacing, int sensorOrientation) { + SystemServices.startListeningForDeviceOrientationChange( + cameraIsFrontFacing, sensorOrientation); + } + + /// Returns a [CameraSelector] based on the specified camera lens direction. + @visibleForTesting + CameraSelector createCameraSelector(int cameraSelectorLensDirection) { + switch (cameraSelectorLensDirection) { + case CameraSelector.lensFacingFront: + return CameraSelector.getDefaultFrontCamera(); + case CameraSelector.lensFacingBack: + return CameraSelector.getDefaultBackCamera(); + default: + return CameraSelector(lensFacing: cameraSelectorLensDirection); + } + } + + /// Returns a [Preview] configured with the specified target rotation and + /// resolution. + @visibleForTesting + Preview createPreview(int targetRotation, ResolutionInfo? targetResolution) { + return Preview( + targetRotation: targetRotation, targetResolution: targetResolution); + } } diff --git a/packages/camera/camera_android_camerax/lib/src/camera_selector.dart b/packages/camera/camera_android_camerax/lib/src/camera_selector.dart index 43a1dabd6906..f1d3c5fdb663 100644 --- a/packages/camera/camera_android_camerax/lib/src/camera_selector.dart +++ b/packages/camera/camera_android_camerax/lib/src/camera_selector.dart @@ -44,10 +44,24 @@ class CameraSelector extends JavaObject { late final CameraSelectorHostApiImpl _api; /// ID for front facing lens. - static const int LENS_FACING_FRONT = 0; + /// + /// See https://developer.android.com/reference/androidx/camera/core/CameraSelector#LENS_FACING_FRONT(). + static const int lensFacingFront = 0; /// ID for back facing lens. - static const int LENS_FACING_BACK = 1; + /// + /// See https://developer.android.com/reference/androidx/camera/core/CameraSelector#LENS_FACING_BACK(). + static const int lensFacingBack = 1; + + /// ID for external lens. + /// + /// See https://developer.android.com/reference/androidx/camera/core/CameraSelector#LENS_FACING_EXTERNAL(). + static const int lensFacingExternal = 2; + + /// ID for unknown lens. + /// + /// See https://developer.android.com/reference/androidx/camera/core/CameraSelector#LENS_FACING_UNKNOWN(). + static const int lensFacingUnknown = -1; /// Selector for default front facing camera. static CameraSelector getDefaultFrontCamera({ @@ -57,7 +71,7 @@ class CameraSelector extends JavaObject { return CameraSelector( binaryMessenger: binaryMessenger, instanceManager: instanceManager, - lensFacing: LENS_FACING_FRONT, + lensFacing: lensFacingFront, ); } @@ -69,7 +83,7 @@ class CameraSelector extends JavaObject { return CameraSelector( binaryMessenger: binaryMessenger, instanceManager: instanceManager, - lensFacing: LENS_FACING_BACK, + lensFacing: lensFacingBack, ); } diff --git a/packages/camera/camera_android_camerax/lib/src/system_services.dart b/packages/camera/camera_android_camerax/lib/src/system_services.dart index 4ca90e257a95..e108b6140bed 100644 --- a/packages/camera/camera_android_camerax/lib/src/system_services.dart +++ b/packages/camera/camera_android_camerax/lib/src/system_services.dart @@ -142,8 +142,6 @@ class SystemServicesFlutterApiImpl implements SystemServicesFlutterApi { /// Callback method for any errors caused by camera usage on the Java side. @override void onCameraError(String errorDescription) { - // TODO(camsim99): Use this to implement onCameraError method in plugin. - // See https://github.com/flutter/flutter/issues/119571 for context. SystemServices.cameraErrorStreamController.add(errorDescription); } } diff --git a/packages/camera/camera_android_camerax/pubspec.yaml b/packages/camera/camera_android_camerax/pubspec.yaml index 45d5a2c66abd..f1496c640497 100644 --- a/packages/camera/camera_android_camerax/pubspec.yaml +++ b/packages/camera/camera_android_camerax/pubspec.yaml @@ -26,8 +26,9 @@ dependencies: stream_transform: ^2.1.0 dev_dependencies: + async: ^2.5.0 build_runner: ^2.1.4 flutter_test: sdk: flutter - mockito: ^5.1.0 + mockito: ^5.3.2 pigeon: ^3.2.6 diff --git a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart index 8b16c64c6ede..acfaf16b9ac4 100644 --- a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart +++ b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart @@ -2,28 +2,43 @@ // 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:async/async.dart'; import 'package:camera_android_camerax/camera_android_camerax.dart'; +import 'package:camera_android_camerax/src/camera.dart'; import 'package:camera_android_camerax/src/camera_info.dart'; import 'package:camera_android_camerax/src/camera_selector.dart'; +import 'package:camera_android_camerax/src/camerax_library.g.dart'; +import 'package:camera_android_camerax/src/preview.dart'; import 'package:camera_android_camerax/src/process_camera_provider.dart'; +import 'package:camera_android_camerax/src/system_services.dart'; +import 'package:camera_android_camerax/src/use_case.dart'; import 'package:camera_platform_interface/camera_platform_interface.dart'; +import 'package:flutter/services.dart' show DeviceOrientation; +import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'android_camera_camerax_test.mocks.dart'; -@GenerateMocks([ - ProcessCameraProvider, - CameraSelector, - CameraInfo, +@GenerateNiceMocks(>[ + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), ]) +@GenerateMocks([BuildContext]) void main() { TestWidgetsFlutterBinding.ensureInitialized(); test('Should fetch CameraDescription instances for available cameras', () async { // Arrange + final MockAndroidCameraCamerax camera = MockAndroidCameraCamerax(); + camera.processCameraProvider = MockProcessCameraProvider(); final List returnData = [ { 'name': 'Camera 0', @@ -37,31 +52,24 @@ void main() { } ]; - //Create mocks to use - final MockProcessCameraProvider mockProcessCameraProvider = - MockProcessCameraProvider(); - final MockCameraSelector mockBackCameraSelector = MockCameraSelector(); - final MockCameraSelector mockFrontCameraSelector = MockCameraSelector(); + // Create mocks to use final MockCameraInfo mockFrontCameraInfo = MockCameraInfo(); final MockCameraInfo mockBackCameraInfo = MockCameraInfo(); - AndroidCameraCameraX.registerWith(); - - //Set class level ProcessCameraProvider and camera selectors to created mocks - final AndroidCameraCameraX androidCameraCamerax = AndroidCameraCameraX(); - androidCameraCamerax.backCameraSelector = mockBackCameraSelector; - androidCameraCamerax.frontCameraSelector = mockFrontCameraSelector; - androidCameraCamerax.processCameraProvider = mockProcessCameraProvider; - //Mock calls to native platform - when(mockProcessCameraProvider.getAvailableCameraInfos()).thenAnswer( + // Mock calls to native platform + when(camera.processCameraProvider!.getAvailableCameraInfos()).thenAnswer( (_) async => [mockBackCameraInfo, mockFrontCameraInfo]); - when(mockBackCameraSelector.filter([mockFrontCameraInfo])) + when(camera.mockBackCameraSelector + .filter([mockFrontCameraInfo])) .thenAnswer((_) async => []); - when(mockBackCameraSelector.filter([mockBackCameraInfo])) + when(camera.mockBackCameraSelector + .filter([mockBackCameraInfo])) .thenAnswer((_) async => [mockBackCameraInfo]); - when(mockFrontCameraSelector.filter([mockBackCameraInfo])) + when(camera.mockFrontCameraSelector + .filter([mockBackCameraInfo])) .thenAnswer((_) async => []); - when(mockFrontCameraSelector.filter([mockFrontCameraInfo])) + when(camera.mockFrontCameraSelector + .filter([mockFrontCameraInfo])) .thenAnswer((_) async => [mockFrontCameraInfo]); when(mockBackCameraInfo.getSensorRotationDegrees()) .thenAnswer((_) async => 0); @@ -69,7 +77,7 @@ void main() { .thenAnswer((_) async => 90); final List cameraDescriptions = - await androidCameraCamerax.availableCameras(); + await camera.availableCameras(); expect(cameraDescriptions.length, returnData.length); for (int i = 0; i < returnData.length; i++) { @@ -85,4 +93,313 @@ void main() { expect(cameraDescriptions[i], cameraDescription); } }); + + test( + 'createCamera requests permissions, starts listening for device orientation changes, and returns flutter surface texture ID', + () async { + final MockAndroidCameraCamerax camera = MockAndroidCameraCamerax(); + camera.processCameraProvider = MockProcessCameraProvider(); + const CameraLensDirection testLensDirection = CameraLensDirection.back; + const int testSensorOrientation = 90; + const CameraDescription testCameraDescription = CameraDescription( + name: 'cameraName', + lensDirection: testLensDirection, + sensorOrientation: testSensorOrientation); + const ResolutionPreset testResolutionPreset = ResolutionPreset.veryHigh; + const bool enableAudio = true; + const int testSurfaceTextureId = 6; + + when(camera.testPreview.setSurfaceProvider()) + .thenAnswer((_) async => testSurfaceTextureId); + + expect( + await camera.createCamera(testCameraDescription, testResolutionPreset, + enableAudio: enableAudio), + equals(testSurfaceTextureId)); + + // Verify permissions are requested and the camera starts listening for device orientation changes. + expect(camera.cameraPermissionsRequested, isTrue); + expect(camera.startedListeningForDeviceOrientationChanges, isTrue); + + // Verify CameraSelector is set with appropriate lens direction. + expect(camera.cameraSelector, equals(camera.mockBackCameraSelector)); + + // Verify the camera's Preview instance is instantiated properly. + expect(camera.preview, equals(camera.testPreview)); + + // Verify the camera's Preview instance has its surface provider set. + verify(camera.preview!.setSurfaceProvider()); + }); + + test( + 'initializeCamera throws AssertionError when createCamera has not been called before initializedCamera', + () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + expect(() => camera.initializeCamera(3), throwsAssertionError); + }); + + test('initializeCamera sends expected CameraInitializedEvent', () async { + final MockAndroidCameraCamerax camera = MockAndroidCameraCamerax(); + camera.processCameraProvider = MockProcessCameraProvider(); + const int cameraId = 10; + const CameraLensDirection testLensDirection = CameraLensDirection.back; + const int testSensorOrientation = 90; + const CameraDescription testCameraDescription = CameraDescription( + name: 'cameraName', + lensDirection: testLensDirection, + sensorOrientation: testSensorOrientation); + const ResolutionPreset testResolutionPreset = ResolutionPreset.veryHigh; + const bool enableAudio = true; + const int resolutionWidth = 350; + const int resolutionHeight = 750; + final Camera mockCamera = MockCamera(); + final ResolutionInfo testResolutionInfo = + ResolutionInfo(width: resolutionWidth, height: resolutionHeight); + + // TODO(camsim99): Modify this when camera configuration is supported and + // defualt values no longer being used. + // https://github.com/flutter/flutter/issues/120468 + // https://github.com/flutter/flutter/issues/120467 + final CameraInitializedEvent testCameraInitializedEvent = + CameraInitializedEvent( + cameraId, + resolutionWidth.toDouble(), + resolutionHeight.toDouble(), + ExposureMode.auto, + false, + FocusMode.auto, + false); + + // Call createCamera. + when(camera.testPreview.setSurfaceProvider()) + .thenAnswer((_) async => cameraId); + await camera.createCamera(testCameraDescription, testResolutionPreset, + enableAudio: enableAudio); + + when(camera.processCameraProvider!.bindToLifecycle( + camera.cameraSelector!, [camera.testPreview])) + .thenAnswer((_) async => mockCamera); + when(camera.testPreview.getResolutionInfo()) + .thenAnswer((_) async => testResolutionInfo); + + // Start listening to camera events stream to verify the proper CameraInitializedEvent is sent. + camera.cameraEventStreamController.stream.listen((CameraEvent event) { + expect(event, const TypeMatcher()); + expect(event, equals(testCameraInitializedEvent)); + }); + + await camera.initializeCamera(cameraId); + + // Verify preview was bound and unbound to get preview resolution information. + verify(camera.processCameraProvider!.bindToLifecycle( + camera.cameraSelector!, [camera.testPreview])); + verify(camera.processCameraProvider!.unbind([camera.testPreview])); + + // Check camera instance was received, but preview is no longer bound. + expect(camera.camera, equals(mockCamera)); + expect(camera.previewIsBound, isFalse); + }); + + test('dispose releases Flutter surface texture and unbinds all use cases', + () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + + camera.preview = MockPreview(); + camera.processCameraProvider = MockProcessCameraProvider(); + + camera.dispose(3); + + verify(camera.preview!.releaseFlutterSurfaceTexture()); + verify(camera.processCameraProvider!.unbindAll()); + }); + + test('onCameraInitialized stream emits CameraInitializedEvents', () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + const int cameraId = 16; + final Stream eventStream = + camera.onCameraInitialized(cameraId); + final StreamQueue streamQueue = + StreamQueue(eventStream); + const CameraInitializedEvent testEvent = CameraInitializedEvent( + cameraId, 320, 80, ExposureMode.auto, false, FocusMode.auto, false); + + camera.cameraEventStreamController.add(testEvent); + + expect(await streamQueue.next, testEvent); + await streamQueue.cancel(); + }); + + test('onCameraError stream emits errors caught by system services', () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + const int cameraId = 27; + const String testErrorDescription = 'Test error description!'; + final Stream eventStream = camera.onCameraError(cameraId); + final StreamQueue streamQueue = + StreamQueue(eventStream); + + SystemServices.cameraErrorStreamController.add(testErrorDescription); + + expect(await streamQueue.next, + equals(const CameraErrorEvent(cameraId, testErrorDescription))); + await streamQueue.cancel(); + }); + + test( + 'onDeviceOrientationChanged stream emits changes in device oreintation detected by system services', + () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + final Stream eventStream = + camera.onDeviceOrientationChanged(); + final StreamQueue streamQueue = + StreamQueue(eventStream); + const DeviceOrientationChangedEvent testEvent = + DeviceOrientationChangedEvent(DeviceOrientation.portraitDown); + + SystemServices.deviceOrientationChangedStreamController.add(testEvent); + + expect(await streamQueue.next, testEvent); + await streamQueue.cancel(); + }); + + test( + 'pausePreview unbinds preview from lifecycle when preview is nonnull and has been bound to lifecycle', + () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + + camera.processCameraProvider = MockProcessCameraProvider(); + camera.preview = MockPreview(); + camera.previewIsBound = true; + + await camera.pausePreview(579); + + verify(camera.processCameraProvider!.unbind([camera.preview!])); + expect(camera.previewIsBound, isFalse); + }); + + test( + 'pausePreview does not unbind preview from lifecycle when preview has not been bound to lifecycle', + () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + + camera.processCameraProvider = MockProcessCameraProvider(); + camera.preview = MockPreview(); + + await camera.pausePreview(632); + + verifyNever( + camera.processCameraProvider!.unbind([camera.preview!])); + }); + + test('resumePreview does not bind preview to lifecycle if already bound', + () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + + camera.processCameraProvider = MockProcessCameraProvider(); + camera.cameraSelector = MockCameraSelector(); + camera.preview = MockPreview(); + camera.previewIsBound = true; + + await camera.resumePreview(78); + + verifyNever(camera.processCameraProvider! + .bindToLifecycle(camera.cameraSelector!, [camera.preview!])); + }); + + test('resumePreview binds preview to lifecycle if not already bound', + () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + + camera.processCameraProvider = MockProcessCameraProvider(); + camera.cameraSelector = MockCameraSelector(); + camera.preview = MockPreview(); + + await camera.resumePreview(78); + + verify(camera.processCameraProvider! + .bindToLifecycle(camera.cameraSelector!, [camera.preview!])); + }); + + test( + 'buildPreview returns a FutureBuilder that does not return a Texture until the preview is bound to the lifecycle', + () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + const int textureId = 75; + + camera.processCameraProvider = MockProcessCameraProvider(); + camera.cameraSelector = MockCameraSelector(); + camera.preview = MockPreview(); + + final FutureBuilder previewWidget = + camera.buildPreview(textureId) as FutureBuilder; + + expect( + previewWidget.builder( + MockBuildContext(), const AsyncSnapshot.nothing()), + isA()); + expect( + previewWidget.builder( + MockBuildContext(), const AsyncSnapshot.waiting()), + isA()); + expect( + previewWidget.builder(MockBuildContext(), + const AsyncSnapshot.withData(ConnectionState.active, null)), + isA()); + }); + + test( + 'buildPreview returns a FutureBuilder that returns a Texture once the preview is bound to the lifecycle', + () async { + final AndroidCameraCameraX camera = AndroidCameraCameraX(); + const int textureId = 75; + + camera.processCameraProvider = MockProcessCameraProvider(); + camera.cameraSelector = MockCameraSelector(); + camera.preview = MockPreview(); + + final FutureBuilder previewWidget = + camera.buildPreview(textureId) as FutureBuilder; + + final Texture previewTexture = previewWidget.builder(MockBuildContext(), + const AsyncSnapshot.withData(ConnectionState.done, null)) + as Texture; + expect(previewTexture.textureId, equals(textureId)); + }); +} + +/// Mock of [AndroidCameraCameraX] that stubs behavior of some methods for +/// testing. +class MockAndroidCameraCamerax extends AndroidCameraCameraX { + bool cameraPermissionsRequested = false; + bool startedListeningForDeviceOrientationChanges = false; + final MockPreview testPreview = MockPreview(); + final MockCameraSelector mockBackCameraSelector = MockCameraSelector(); + final MockCameraSelector mockFrontCameraSelector = MockCameraSelector(); + + @override + Future requestCameraPermissions(bool enableAudio) async { + cameraPermissionsRequested = true; + } + + @override + void startListeningForDeviceOrientationChange( + bool cameraIsFrontFacing, int sensorOrientation) { + startedListeningForDeviceOrientationChanges = true; + return; + } + + @override + CameraSelector createCameraSelector(int cameraSelectorLensDirection) { + switch (cameraSelectorLensDirection) { + case CameraSelector.lensFacingFront: + return mockFrontCameraSelector; + case CameraSelector.lensFacingBack: + default: + return mockBackCameraSelector; + } + } + + @override + Preview createPreview(int targetRotation, ResolutionInfo? targetResolution) { + return testPreview; + } } diff --git a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.mocks.dart b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.mocks.dart index 44171eebda91..af225a10c64a 100644 --- a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.mocks.dart +++ b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.mocks.dart @@ -3,11 +3,20 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i3; +import 'dart:async' as _i8; -import 'package:camera_android_camerax/src/camera_info.dart' as _i4; -import 'package:camera_android_camerax/src/camera_selector.dart' as _i5; -import 'package:camera_android_camerax/src/process_camera_provider.dart' as _i2; +import 'package:camera_android_camerax/src/camera.dart' as _i3; +import 'package:camera_android_camerax/src/camera_info.dart' as _i7; +import 'package:camera_android_camerax/src/camera_selector.dart' as _i9; +import 'package:camera_android_camerax/src/camerax_library.g.dart' as _i2; +import 'package:camera_android_camerax/src/preview.dart' as _i10; +import 'package:camera_android_camerax/src/process_camera_provider.dart' + as _i11; +import 'package:camera_android_camerax/src/use_case.dart' as _i12; +import 'package:flutter/foundation.dart' as _i6; +import 'package:flutter/services.dart' as _i5; +import 'package:flutter/src/widgets/framework.dart' as _i4; +import 'package:flutter/src/widgets/notification_listener.dart' as _i13; import 'package:mockito/mockito.dart' as _i1; // ignore_for_file: type=lint @@ -21,59 +30,360 @@ import 'package:mockito/mockito.dart' as _i1; // ignore_for_file: camel_case_types // ignore_for_file: subtype_of_sealed_class -/// A class which mocks [ProcessCameraProvider]. +class _FakeResolutionInfo_0 extends _i1.SmartFake + implements _i2.ResolutionInfo { + _FakeResolutionInfo_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeCamera_1 extends _i1.SmartFake implements _i3.Camera { + _FakeCamera_1( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeWidget_2 extends _i1.SmartFake implements _i4.Widget { + _FakeWidget_2( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); + + @override + String toString({_i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info}) => + super.toString(); +} + +class _FakeInheritedWidget_3 extends _i1.SmartFake + implements _i4.InheritedWidget { + _FakeInheritedWidget_3( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); + + @override + String toString({_i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info}) => + super.toString(); +} + +class _FakeDiagnosticsNode_4 extends _i1.SmartFake + implements _i6.DiagnosticsNode { + _FakeDiagnosticsNode_4( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); + + @override + String toString({ + _i6.TextTreeConfiguration? parentConfiguration, + _i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info, + }) => + super.toString(); +} + +/// A class which mocks [Camera]. /// /// See the documentation for Mockito's code generation for more information. -class MockProcessCameraProvider extends _i1.Mock - implements _i2.ProcessCameraProvider { - MockProcessCameraProvider() { - _i1.throwOnMissingStub(this); - } +class MockCamera extends _i1.Mock implements _i3.Camera {} +/// A class which mocks [CameraInfo]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockCameraInfo extends _i1.Mock implements _i7.CameraInfo { @override - _i3.Future> getAvailableCameraInfos() => - (super.noSuchMethod( + _i8.Future getSensorRotationDegrees() => (super.noSuchMethod( Invocation.method( - #getAvailableCameraInfos, + #getSensorRotationDegrees, [], ), - returnValue: _i3.Future>.value(<_i4.CameraInfo>[]), - ) as _i3.Future>); + returnValue: _i8.Future.value(0), + returnValueForMissingStub: _i8.Future.value(0), + ) as _i8.Future); } /// A class which mocks [CameraSelector]. /// /// See the documentation for Mockito's code generation for more information. -class MockCameraSelector extends _i1.Mock implements _i5.CameraSelector { - MockCameraSelector() { - _i1.throwOnMissingStub(this); - } - +class MockCameraSelector extends _i1.Mock implements _i9.CameraSelector { @override - _i3.Future> filter(List<_i4.CameraInfo>? cameraInfos) => + _i8.Future> filter(List<_i7.CameraInfo>? cameraInfos) => (super.noSuchMethod( Invocation.method( #filter, [cameraInfos], ), - returnValue: _i3.Future>.value(<_i4.CameraInfo>[]), - ) as _i3.Future>); + returnValue: _i8.Future>.value(<_i7.CameraInfo>[]), + returnValueForMissingStub: + _i8.Future>.value(<_i7.CameraInfo>[]), + ) as _i8.Future>); } -/// A class which mocks [CameraInfo]. +/// A class which mocks [Preview]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockPreview extends _i1.Mock implements _i10.Preview { + @override + _i8.Future setSurfaceProvider() => (super.noSuchMethod( + Invocation.method( + #setSurfaceProvider, + [], + ), + returnValue: _i8.Future.value(0), + returnValueForMissingStub: _i8.Future.value(0), + ) as _i8.Future); + @override + void releaseFlutterSurfaceTexture() => super.noSuchMethod( + Invocation.method( + #releaseFlutterSurfaceTexture, + [], + ), + returnValueForMissingStub: null, + ); + @override + _i8.Future<_i2.ResolutionInfo> getResolutionInfo() => (super.noSuchMethod( + Invocation.method( + #getResolutionInfo, + [], + ), + returnValue: _i8.Future<_i2.ResolutionInfo>.value(_FakeResolutionInfo_0( + this, + Invocation.method( + #getResolutionInfo, + [], + ), + )), + returnValueForMissingStub: + _i8.Future<_i2.ResolutionInfo>.value(_FakeResolutionInfo_0( + this, + Invocation.method( + #getResolutionInfo, + [], + ), + )), + ) as _i8.Future<_i2.ResolutionInfo>); +} + +/// A class which mocks [ProcessCameraProvider]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockProcessCameraProvider extends _i1.Mock + implements _i11.ProcessCameraProvider { + @override + _i8.Future> getAvailableCameraInfos() => + (super.noSuchMethod( + Invocation.method( + #getAvailableCameraInfos, + [], + ), + returnValue: _i8.Future>.value(<_i7.CameraInfo>[]), + returnValueForMissingStub: + _i8.Future>.value(<_i7.CameraInfo>[]), + ) as _i8.Future>); + @override + _i8.Future<_i3.Camera> bindToLifecycle( + _i9.CameraSelector? cameraSelector, + List<_i12.UseCase>? useCases, + ) => + (super.noSuchMethod( + Invocation.method( + #bindToLifecycle, + [ + cameraSelector, + useCases, + ], + ), + returnValue: _i8.Future<_i3.Camera>.value(_FakeCamera_1( + this, + Invocation.method( + #bindToLifecycle, + [ + cameraSelector, + useCases, + ], + ), + )), + returnValueForMissingStub: _i8.Future<_i3.Camera>.value(_FakeCamera_1( + this, + Invocation.method( + #bindToLifecycle, + [ + cameraSelector, + useCases, + ], + ), + )), + ) as _i8.Future<_i3.Camera>); + @override + void unbind(List<_i12.UseCase>? useCases) => super.noSuchMethod( + Invocation.method( + #unbind, + [useCases], + ), + returnValueForMissingStub: null, + ); + @override + void unbindAll() => super.noSuchMethod( + Invocation.method( + #unbindAll, + [], + ), + returnValueForMissingStub: null, + ); +} + +/// A class which mocks [BuildContext]. /// /// See the documentation for Mockito's code generation for more information. -class MockCameraInfo extends _i1.Mock implements _i4.CameraInfo { - MockCameraInfo() { +class MockBuildContext extends _i1.Mock implements _i4.BuildContext { + MockBuildContext() { _i1.throwOnMissingStub(this); } @override - _i3.Future getSensorRotationDegrees() => (super.noSuchMethod( + _i4.Widget get widget => (super.noSuchMethod( + Invocation.getter(#widget), + returnValue: _FakeWidget_2( + this, + Invocation.getter(#widget), + ), + ) as _i4.Widget); + @override + bool get mounted => (super.noSuchMethod( + Invocation.getter(#mounted), + returnValue: false, + ) as bool); + @override + bool get debugDoingBuild => (super.noSuchMethod( + Invocation.getter(#debugDoingBuild), + returnValue: false, + ) as bool); + @override + _i4.InheritedWidget dependOnInheritedElement( + _i4.InheritedElement? ancestor, { + Object? aspect, + }) => + (super.noSuchMethod( Invocation.method( - #getSensorRotationDegrees, + #dependOnInheritedElement, + [ancestor], + {#aspect: aspect}, + ), + returnValue: _FakeInheritedWidget_3( + this, + Invocation.method( + #dependOnInheritedElement, + [ancestor], + {#aspect: aspect}, + ), + ), + ) as _i4.InheritedWidget); + @override + void visitAncestorElements(bool Function(_i4.Element)? visitor) => + super.noSuchMethod( + Invocation.method( + #visitAncestorElements, + [visitor], + ), + returnValueForMissingStub: null, + ); + @override + void visitChildElements(_i4.ElementVisitor? visitor) => super.noSuchMethod( + Invocation.method( + #visitChildElements, + [visitor], + ), + returnValueForMissingStub: null, + ); + @override + void dispatchNotification(_i13.Notification? notification) => + super.noSuchMethod( + Invocation.method( + #dispatchNotification, + [notification], + ), + returnValueForMissingStub: null, + ); + @override + _i6.DiagnosticsNode describeElement( + String? name, { + _i6.DiagnosticsTreeStyle? style = _i6.DiagnosticsTreeStyle.errorProperty, + }) => + (super.noSuchMethod( + Invocation.method( + #describeElement, + [name], + {#style: style}, + ), + returnValue: _FakeDiagnosticsNode_4( + this, + Invocation.method( + #describeElement, + [name], + {#style: style}, + ), + ), + ) as _i6.DiagnosticsNode); + @override + _i6.DiagnosticsNode describeWidget( + String? name, { + _i6.DiagnosticsTreeStyle? style = _i6.DiagnosticsTreeStyle.errorProperty, + }) => + (super.noSuchMethod( + Invocation.method( + #describeWidget, + [name], + {#style: style}, + ), + returnValue: _FakeDiagnosticsNode_4( + this, + Invocation.method( + #describeWidget, + [name], + {#style: style}, + ), + ), + ) as _i6.DiagnosticsNode); + @override + List<_i6.DiagnosticsNode> describeMissingAncestor( + {required Type? expectedAncestorType}) => + (super.noSuchMethod( + Invocation.method( + #describeMissingAncestor, [], + {#expectedAncestorType: expectedAncestorType}, + ), + returnValue: <_i6.DiagnosticsNode>[], + ) as List<_i6.DiagnosticsNode>); + @override + _i6.DiagnosticsNode describeOwnershipChain(String? name) => + (super.noSuchMethod( + Invocation.method( + #describeOwnershipChain, + [name], + ), + returnValue: _FakeDiagnosticsNode_4( + this, + Invocation.method( + #describeOwnershipChain, + [name], + ), ), - returnValue: _i3.Future.value(0), - ) as _i3.Future); + ) as _i6.DiagnosticsNode); } diff --git a/packages/camera/camera_android_camerax/test/camera_selector_test.dart b/packages/camera/camera_android_camerax/test/camera_selector_test.dart index 54f7864fb85f..52f9a18d956e 100644 --- a/packages/camera/camera_android_camerax/test/camera_selector_test.dart +++ b/packages/camera/camera_android_camerax/test/camera_selector_test.dart @@ -60,10 +60,10 @@ void main() { ); CameraSelector( instanceManager: instanceManager, - lensFacing: CameraSelector.LENS_FACING_BACK); + lensFacing: CameraSelector.lensFacingBack); verify( - mockApi.create(argThat(isA()), CameraSelector.LENS_FACING_BACK)); + mockApi.create(argThat(isA()), CameraSelector.lensFacingBack)); }); test('filterTest', () async { @@ -108,14 +108,14 @@ void main() { instanceManager: instanceManager, ); - flutterApi.create(0, CameraSelector.LENS_FACING_BACK); + flutterApi.create(0, CameraSelector.lensFacingBack); expect(instanceManager.getInstanceWithWeakReference(0), isA()); expect( (instanceManager.getInstanceWithWeakReference(0)! as CameraSelector) .lensFacing, - equals(CameraSelector.LENS_FACING_BACK)); + equals(CameraSelector.lensFacingBack)); }); }); } From 48f50b4f13efba59977feedcacc0bdb39e788c48 Mon Sep 17 00:00:00 2001 From: Jenn Magder Date: Fri, 17 Feb 2023 12:17:49 -0800 Subject: [PATCH 113/130] [image_picker] Update NSPhotoLibraryUsageDescription description in README (#6589) * Update `NSPhotoLibraryUsageDescription` description in README * Update text about App Store policy --- packages/image_picker/image_picker/CHANGELOG.md | 4 +++- packages/image_picker/image_picker/README.md | 2 +- packages/image_picker/image_picker/pubspec.yaml | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/image_picker/image_picker/CHANGELOG.md b/packages/image_picker/image_picker/CHANGELOG.md index 1f138ef26118..1ac6a8d77ba2 100644 --- a/packages/image_picker/image_picker/CHANGELOG.md +++ b/packages/image_picker/image_picker/CHANGELOG.md @@ -1,4 +1,6 @@ -## NEXT +## 0.8.6+2 + +* Updates `NSPhotoLibraryUsageDescription` description in README. * Updates minimum Flutter version to 3.0. diff --git a/packages/image_picker/image_picker/README.md b/packages/image_picker/image_picker/README.md index aadfc83ff5e6..8fff8920054c 100755 --- a/packages/image_picker/image_picker/README.md +++ b/packages/image_picker/image_picker/README.md @@ -23,7 +23,7 @@ As a result of implementing PHPicker it becomes impossible to pick HEIC images o Add the following keys to your _Info.plist_ file, located in `/ios/Runner/Info.plist`: * `NSPhotoLibraryUsageDescription` - describe why your app needs permission for the photo library. This is called _Privacy - Photo Library Usage Description_ in the visual editor. - * This permission is not required for image picking on iOS 11+ if you pass `false` for `requestFullMetadata`. + * This permission will not be requested if you always pass `false` for `requestFullMetadata`, but App Store policy requires including the plist entry. * `NSCameraUsageDescription` - describe why your app needs access to the camera. This is called _Privacy - Camera Usage Description_ in the visual editor. * `NSMicrophoneUsageDescription` - describe why your app needs access to the microphone, if you intend to record videos. This is called _Privacy - Microphone Usage Description_ in the visual editor. diff --git a/packages/image_picker/image_picker/pubspec.yaml b/packages/image_picker/image_picker/pubspec.yaml index 7bd8ecfd9b9b..0d6308198891 100755 --- a/packages/image_picker/image_picker/pubspec.yaml +++ b/packages/image_picker/image_picker/pubspec.yaml @@ -3,7 +3,7 @@ description: Flutter plugin for selecting images from the Android and iOS image library, and taking new pictures with the camera. repository: https://github.com/flutter/plugins/tree/main/packages/image_picker/image_picker issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+image_picker%22 -version: 0.8.6+1 +version: 0.8.6+2 environment: sdk: ">=2.14.0 <3.0.0" From eea17c996f7a8adcd89bc7637e1751de6b651427 Mon Sep 17 00:00:00 2001 From: Ian Hickson Date: Fri, 17 Feb 2023 12:49:06 -0800 Subject: [PATCH 114/130] Migrate these tests to the "new" API. (#7189) --- .../test/method_channel_mock.dart | 10 +- .../test/method_channel_mock.dart | 10 +- .../test/utils/method_channel_mock.dart | 10 +- .../test/utils/method_channel_mock.dart | 10 +- .../test/file_selector_linux_test.dart | 19 +++- .../method_channel_file_selector_test.dart | 19 +++- .../test/circle_updates_test.dart | 14 ++- .../test/fake_maps_controllers.dart | 10 +- .../test/google_map_test.dart | 14 ++- .../test/marker_updates_test.dart | 14 ++- .../test/polygon_updates_test.dart | 14 ++- .../test/polyline_updates_test.dart | 14 ++- .../test/tile_overlay_updates_test.dart | 14 ++- .../google_maps_flutter_android_test.dart | 16 ++- .../test/google_maps_flutter_ios_test.dart | 16 ++- ...thod_channel_google_maps_flutter_test.dart | 16 ++- .../test/google_sign_in_android_test.dart | 27 +++-- .../test/google_sign_in_ios_test.dart | 27 +++-- .../method_channel_google_sign_in_test.dart | 10 +- .../test/image_picker_android_test.dart | 91 ++++++++++++--- .../new_method_channel_image_picker_test.dart | 104 +++++++++++++----- .../billing_client_wrapper_test.dart | 11 +- ...rchase_android_platform_addition_test.dart | 10 +- ...in_app_purchase_android_platform_test.dart | 10 +- .../test/fakes/fake_storekit_platform.dart | 10 +- ...rchase_storekit_platform_addtion_test.dart | 12 +- ...n_app_purchase_storekit_platform_test.dart | 12 +- .../sk_methodchannel_apis_test.dart | 16 ++- .../sk_payment_queue_delegate_api_test.dart | 16 ++- .../test/ios_platform_images_test.dart | 14 ++- .../test/local_auth_test.dart | 10 +- .../local_auth_ios/test/local_auth_test.dart | 10 +- .../default_method_channel_platform_test.dart | 18 ++- .../method_channel_path_provider_test.dart | 12 +- .../test/quick_actions_android_test.dart | 12 +- .../test/quick_actions_ios_test.dart | 12 +- .../method_channel_quick_actions_test.dart | 12 +- .../test/shared_preferences_android_test.dart | 10 +- ...ethod_channel_shared_preferences_test.dart | 10 +- .../test/url_launcher_android_test.dart | 26 ++++- .../test/url_launcher_ios_test.dart | 10 +- .../test/url_launcher_linux_test.dart | 10 +- .../test/url_launcher_macos_test.dart | 10 +- .../method_channel_url_launcher_test.dart | 10 +- .../test/android_video_player_test.dart | 28 ++--- .../test/avfoundation_video_player_test.dart | 24 ++-- .../test/legacy/surface_android_test.dart | 16 ++- 47 files changed, 658 insertions(+), 172 deletions(-) diff --git a/packages/camera/camera_android/test/method_channel_mock.dart b/packages/camera/camera_android/test/method_channel_mock.dart index 413c10633cc1..f26d12a3688a 100644 --- a/packages/camera/camera_android/test/method_channel_mock.dart +++ b/packages/camera/camera_android/test/method_channel_mock.dart @@ -11,7 +11,9 @@ class MethodChannelMock { this.delay, required this.methods, }) : methodChannel = MethodChannel(channelName) { - methodChannel.setMockMethodCallHandler(_handler); + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler(methodChannel, _handler); } final Duration? delay; @@ -37,3 +39,9 @@ class MethodChannelMock { }); } } + +/// This allows a value of type T or T? to be treated as a value of type T?. +/// +/// We use this so that APIs that have become non-nullable can still be used +/// with `!` and `?` on the stable branch. +T? _ambiguate(T? value) => value; diff --git a/packages/camera/camera_avfoundation/test/method_channel_mock.dart b/packages/camera/camera_avfoundation/test/method_channel_mock.dart index 413c10633cc1..f26d12a3688a 100644 --- a/packages/camera/camera_avfoundation/test/method_channel_mock.dart +++ b/packages/camera/camera_avfoundation/test/method_channel_mock.dart @@ -11,7 +11,9 @@ class MethodChannelMock { this.delay, required this.methods, }) : methodChannel = MethodChannel(channelName) { - methodChannel.setMockMethodCallHandler(_handler); + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler(methodChannel, _handler); } final Duration? delay; @@ -37,3 +39,9 @@ class MethodChannelMock { }); } } + +/// This allows a value of type T or T? to be treated as a value of type T?. +/// +/// We use this so that APIs that have become non-nullable can still be used +/// with `!` and `?` on the stable branch. +T? _ambiguate(T? value) => value; diff --git a/packages/camera/camera_platform_interface/test/utils/method_channel_mock.dart b/packages/camera/camera_platform_interface/test/utils/method_channel_mock.dart index 413c10633cc1..f26d12a3688a 100644 --- a/packages/camera/camera_platform_interface/test/utils/method_channel_mock.dart +++ b/packages/camera/camera_platform_interface/test/utils/method_channel_mock.dart @@ -11,7 +11,9 @@ class MethodChannelMock { this.delay, required this.methods, }) : methodChannel = MethodChannel(channelName) { - methodChannel.setMockMethodCallHandler(_handler); + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler(methodChannel, _handler); } final Duration? delay; @@ -37,3 +39,9 @@ class MethodChannelMock { }); } } + +/// This allows a value of type T or T? to be treated as a value of type T?. +/// +/// We use this so that APIs that have become non-nullable can still be used +/// with `!` and `?` on the stable branch. +T? _ambiguate(T? value) => value; diff --git a/packages/camera/camera_windows/test/utils/method_channel_mock.dart b/packages/camera/camera_windows/test/utils/method_channel_mock.dart index 22f7ecead589..559f60662844 100644 --- a/packages/camera/camera_windows/test/utils/method_channel_mock.dart +++ b/packages/camera/camera_windows/test/utils/method_channel_mock.dart @@ -17,7 +17,9 @@ class MethodChannelMock { this.delay, required this.methods, }) : methodChannel = MethodChannel(channelName) { - methodChannel.setMockMethodCallHandler(_handler); + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler(methodChannel, _handler); } final Duration? delay; @@ -43,3 +45,9 @@ class MethodChannelMock { }); } } + +/// This allows a value of type T or T? to be treated as a value of type T?. +/// +/// We use this so that APIs that have become non-nullable can still be used +/// with `!` and `?` on the stable branch. +T? _ambiguate(T? value) => value; diff --git a/packages/file_selector/file_selector_linux/test/file_selector_linux_test.dart b/packages/file_selector/file_selector_linux/test/file_selector_linux_test.dart index 4eae078def0c..53a549da3d4a 100644 --- a/packages/file_selector/file_selector_linux/test/file_selector_linux_test.dart +++ b/packages/file_selector/file_selector_linux/test/file_selector_linux_test.dart @@ -16,10 +16,15 @@ void main() { setUp(() { plugin = FileSelectorLinux(); log = []; - plugin.channel.setMockMethodCallHandler((MethodCall methodCall) async { - log.add(methodCall); - return null; - }); + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler( + plugin.channel, + (MethodCall methodCall) async { + log.add(methodCall); + return null; + }, + ); }); test('registers instance', () { @@ -424,3 +429,9 @@ void expectMethodCall( }) { expect(log, [isMethodCall(methodName, arguments: arguments)]); } + +/// This allows a value of type T or T? to be treated as a value of type T?. +/// +/// We use this so that APIs that have become non-nullable can still be used +/// with `!` and `?` on the stable branch. +T? _ambiguate(T? value) => value; diff --git a/packages/file_selector/file_selector_platform_interface/test/method_channel_file_selector_test.dart b/packages/file_selector/file_selector_platform_interface/test/method_channel_file_selector_test.dart index 9caa76c02bcb..c5438f7ecbc2 100644 --- a/packages/file_selector/file_selector_platform_interface/test/method_channel_file_selector_test.dart +++ b/packages/file_selector/file_selector_platform_interface/test/method_channel_file_selector_test.dart @@ -16,10 +16,15 @@ void main() { final List log = []; setUp(() { - plugin.channel.setMockMethodCallHandler((MethodCall methodCall) async { - log.add(methodCall); - return null; - }); + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler( + plugin.channel, + (MethodCall methodCall) async { + log.add(methodCall); + return null; + }, + ); log.clear(); }); @@ -274,3 +279,9 @@ void expectMethodCall( }) { expect(log, [isMethodCall(methodName, arguments: arguments)]); } + +/// This allows a value of type T or T? to be treated as a value of type T?. +/// +/// We use this so that APIs that have become non-nullable can still be used +/// with `!` and `?` on the stable branch. +T? _ambiguate(T? value) => value; diff --git a/packages/google_maps_flutter/google_maps_flutter/test/circle_updates_test.dart b/packages/google_maps_flutter/google_maps_flutter/test/circle_updates_test.dart index c6e71137903b..459e16b60c42 100644 --- a/packages/google_maps_flutter/google_maps_flutter/test/circle_updates_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter/test/circle_updates_test.dart @@ -26,8 +26,12 @@ void main() { FakePlatformViewsController(); setUpAll(() { - SystemChannels.platform_views.setMockMethodCallHandler( - fakePlatformViewsController.fakePlatformViewsMethodHandler); + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler( + SystemChannels.platform_views, + fakePlatformViewsController.fakePlatformViewsMethodHandler, + ); }); setUp(() { @@ -194,3 +198,9 @@ void main() { expect(platformGoogleMap.circlesToAdd.isEmpty, true); }); } + +/// This allows a value of type T or T? to be treated as a value of type T?. +/// +/// We use this so that APIs that have become non-nullable can still be used +/// with `!` and `?` on the stable branch. +T? _ambiguate(T? value) => value; diff --git a/packages/google_maps_flutter/google_maps_flutter/test/fake_maps_controllers.dart b/packages/google_maps_flutter/google_maps_flutter/test/fake_maps_controllers.dart index b2747c9cb5fe..2c6aba1bb0ba 100644 --- a/packages/google_maps_flutter/google_maps_flutter/test/fake_maps_controllers.dart +++ b/packages/google_maps_flutter/google_maps_flutter/test/fake_maps_controllers.dart @@ -13,7 +13,9 @@ class FakePlatformGoogleMap { : cameraPosition = CameraPosition.fromMap(params['initialCameraPosition']), channel = MethodChannel('plugins.flutter.io/google_maps_$id') { - channel.setMockMethodCallHandler(onMethodCall); + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler(channel, onMethodCall); updateOptions(params['options'] as Map); updateMarkers(params); updatePolygons(params); @@ -478,3 +480,9 @@ Map? _decodeParams(Uint8List paramsMessage) { return const StandardMessageCodec().decodeMessage(messageBytes) as Map?; } + +/// This allows a value of type T or T? to be treated as a value of type T?. +/// +/// We use this so that APIs that have become non-nullable can still be used +/// with `!` and `?` on the stable branch. +T? _ambiguate(T? value) => value; diff --git a/packages/google_maps_flutter/google_maps_flutter/test/google_map_test.dart b/packages/google_maps_flutter/google_maps_flutter/test/google_map_test.dart index 0fc5e7723df5..99b12988f3b4 100644 --- a/packages/google_maps_flutter/google_maps_flutter/test/google_map_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter/test/google_map_test.dart @@ -16,8 +16,12 @@ void main() { FakePlatformViewsController(); setUpAll(() { - SystemChannels.platform_views.setMockMethodCallHandler( - fakePlatformViewsController.fakePlatformViewsMethodHandler); + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler( + SystemChannels.platform_views, + fakePlatformViewsController.fakePlatformViewsMethodHandler, + ); }); setUp(() { @@ -576,3 +580,9 @@ void main() { expect(platformGoogleMap.buildingsEnabled, true); }); } + +/// This allows a value of type T or T? to be treated as a value of type T?. +/// +/// We use this so that APIs that have become non-nullable can still be used +/// with `!` and `?` on the stable branch. +T? _ambiguate(T? value) => value; diff --git a/packages/google_maps_flutter/google_maps_flutter/test/marker_updates_test.dart b/packages/google_maps_flutter/google_maps_flutter/test/marker_updates_test.dart index 0acde97e8d9a..75a153e0eaa2 100644 --- a/packages/google_maps_flutter/google_maps_flutter/test/marker_updates_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter/test/marker_updates_test.dart @@ -26,8 +26,12 @@ void main() { FakePlatformViewsController(); setUpAll(() { - SystemChannels.platform_views.setMockMethodCallHandler( - fakePlatformViewsController.fakePlatformViewsMethodHandler); + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler( + SystemChannels.platform_views, + fakePlatformViewsController.fakePlatformViewsMethodHandler, + ); }); setUp(() { @@ -200,3 +204,9 @@ void main() { expect(platformGoogleMap.markersToAdd.isEmpty, true); }); } + +/// This allows a value of type T or T? to be treated as a value of type T?. +/// +/// We use this so that APIs that have become non-nullable can still be used +/// with `!` and `?` on the stable branch. +T? _ambiguate(T? value) => value; diff --git a/packages/google_maps_flutter/google_maps_flutter/test/polygon_updates_test.dart b/packages/google_maps_flutter/google_maps_flutter/test/polygon_updates_test.dart index 1e5ea84a0422..152cbddfc34a 100644 --- a/packages/google_maps_flutter/google_maps_flutter/test/polygon_updates_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter/test/polygon_updates_test.dart @@ -49,8 +49,12 @@ void main() { FakePlatformViewsController(); setUpAll(() { - SystemChannels.platform_views.setMockMethodCallHandler( - fakePlatformViewsController.fakePlatformViewsMethodHandler); + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler( + SystemChannels.platform_views, + fakePlatformViewsController.fakePlatformViewsMethodHandler, + ); }); setUp(() { @@ -403,3 +407,9 @@ void main() { expect(platformGoogleMap.polygonsToAdd.isEmpty, true); }); } + +/// This allows a value of type T or T? to be treated as a value of type T?. +/// +/// We use this so that APIs that have become non-nullable can still be used +/// with `!` and `?` on the stable branch. +T? _ambiguate(T? value) => value; diff --git a/packages/google_maps_flutter/google_maps_flutter/test/polyline_updates_test.dart b/packages/google_maps_flutter/google_maps_flutter/test/polyline_updates_test.dart index 4c68a71542d5..03b6c620190a 100644 --- a/packages/google_maps_flutter/google_maps_flutter/test/polyline_updates_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter/test/polyline_updates_test.dart @@ -26,8 +26,12 @@ void main() { FakePlatformViewsController(); setUpAll(() { - SystemChannels.platform_views.setMockMethodCallHandler( - fakePlatformViewsController.fakePlatformViewsMethodHandler); + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler( + SystemChannels.platform_views, + fakePlatformViewsController.fakePlatformViewsMethodHandler, + ); }); setUp(() { @@ -216,3 +220,9 @@ void main() { expect(platformGoogleMap.polylinesToAdd.isEmpty, true); }); } + +/// This allows a value of type T or T? to be treated as a value of type T?. +/// +/// We use this so that APIs that have become non-nullable can still be used +/// with `!` and `?` on the stable branch. +T? _ambiguate(T? value) => value; diff --git a/packages/google_maps_flutter/google_maps_flutter/test/tile_overlay_updates_test.dart b/packages/google_maps_flutter/google_maps_flutter/test/tile_overlay_updates_test.dart index b4586f743296..e4e4514dd501 100644 --- a/packages/google_maps_flutter/google_maps_flutter/test/tile_overlay_updates_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter/test/tile_overlay_updates_test.dart @@ -24,8 +24,12 @@ void main() { FakePlatformViewsController(); setUpAll(() { - SystemChannels.platform_views.setMockMethodCallHandler( - fakePlatformViewsController.fakePlatformViewsMethodHandler); + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler( + SystemChannels.platform_views, + fakePlatformViewsController.fakePlatformViewsMethodHandler, + ); }); setUp(() { @@ -198,3 +202,9 @@ void main() { expect(platformGoogleMap.tileOverlaysToAdd.isEmpty, true); }); } + +/// This allows a value of type T or T? to be treated as a value of type T?. +/// +/// We use this so that APIs that have become non-nullable can still be used +/// with `!` and `?` on the stable branch. +T? _ambiguate(T? value) => value; diff --git a/packages/google_maps_flutter/google_maps_flutter_android/test/google_maps_flutter_android_test.dart b/packages/google_maps_flutter/google_maps_flutter_android/test/google_maps_flutter_android_test.dart index 6f9edad9cb71..29c02c836a85 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/test/google_maps_flutter_android_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter_android/test/google_maps_flutter_android_test.dart @@ -25,12 +25,16 @@ void main() { required int mapId, required Future? Function(MethodCall call) handler, }) { - maps - .ensureChannelInitialized(mapId) - .setMockMethodCallHandler((MethodCall methodCall) { - log.add(methodCall.method); - return handler(methodCall); - }); + final MethodChannel channel = maps.ensureChannelInitialized(mapId); + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler( + channel, + (MethodCall methodCall) { + log.add(methodCall.method); + return handler(methodCall); + }, + ); } Future sendPlatformMessage( diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/test/google_maps_flutter_ios_test.dart b/packages/google_maps_flutter/google_maps_flutter_ios/test/google_maps_flutter_ios_test.dart index fb23ab24aaeb..a5d376da1684 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/test/google_maps_flutter_ios_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter_ios/test/google_maps_flutter_ios_test.dart @@ -24,12 +24,16 @@ void main() { required int mapId, required Future? Function(MethodCall call) handler, }) { - maps - .ensureChannelInitialized(mapId) - .setMockMethodCallHandler((MethodCall methodCall) { - log.add(methodCall.method); - return handler(methodCall); - }); + final MethodChannel channel = maps.ensureChannelInitialized(mapId); + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler( + channel, + (MethodCall methodCall) { + log.add(methodCall.method); + return handler(methodCall); + }, + ); } Future sendPlatformMessage( diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/method_channel/method_channel_google_maps_flutter_test.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/method_channel/method_channel_google_maps_flutter_test.dart index 18743dd1f00e..ef37c2f221fa 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/method_channel/method_channel_google_maps_flutter_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/method_channel/method_channel_google_maps_flutter_test.dart @@ -24,12 +24,16 @@ void main() { required int mapId, required Future? Function(MethodCall call) handler, }) { - maps - .ensureChannelInitialized(mapId) - .setMockMethodCallHandler((MethodCall methodCall) { - log.add(methodCall.method); - return handler(methodCall); - }); + final MethodChannel channel = maps.ensureChannelInitialized(mapId); + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler( + channel, + (MethodCall methodCall) { + log.add(methodCall.method); + return handler(methodCall); + }, + ); } Future sendPlatformMessage( diff --git a/packages/google_sign_in/google_sign_in_android/test/google_sign_in_android_test.dart b/packages/google_sign_in/google_sign_in_android/test/google_sign_in_android_test.dart index 2565836f51aa..b70d2e7bffa6 100644 --- a/packages/google_sign_in/google_sign_in_android/test/google_sign_in_android_test.dart +++ b/packages/google_sign_in/google_sign_in_android/test/google_sign_in_android_test.dart @@ -50,14 +50,19 @@ void main() { setUp(() { responses = Map.from(kDefaultResponses); - channel.setMockMethodCallHandler((MethodCall methodCall) { - log.add(methodCall); - final dynamic response = responses[methodCall.method]; - if (response != null && response is Exception) { - return Future.error('$response'); - } - return Future.value(response); - }); + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler( + channel, + (MethodCall methodCall) { + log.add(methodCall); + final dynamic response = responses[methodCall.method]; + if (response != null && response is Exception) { + return Future.error('$response'); + } + return Future.value(response); + }, + ); log.clear(); }); @@ -159,3 +164,9 @@ void main() { expect(log, tests.values); }); } + +/// This allows a value of type T or T? to be treated as a value of type T?. +/// +/// We use this so that APIs that have become non-nullable can still be used +/// with `!` and `?` on the stable branch. +T? _ambiguate(T? value) => value; diff --git a/packages/google_sign_in/google_sign_in_ios/test/google_sign_in_ios_test.dart b/packages/google_sign_in/google_sign_in_ios/test/google_sign_in_ios_test.dart index 0fee1af66120..6adbdec39b74 100644 --- a/packages/google_sign_in/google_sign_in_ios/test/google_sign_in_ios_test.dart +++ b/packages/google_sign_in/google_sign_in_ios/test/google_sign_in_ios_test.dart @@ -51,14 +51,19 @@ void main() { setUp(() { responses = Map.from(kDefaultResponses); log = []; - channel.setMockMethodCallHandler((MethodCall methodCall) { - log.add(methodCall); - final dynamic response = responses[methodCall.method]; - if (response != null && response is Exception) { - return Future.error('$response'); - } - return Future.value(response); - }); + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler( + channel, + (MethodCall methodCall) { + log.add(methodCall); + final dynamic response = responses[methodCall.method]; + if (response != null && response is Exception) { + return Future.error('$response'); + } + return Future.value(response); + }, + ); }); test('registered instance', () { @@ -162,3 +167,9 @@ void main() { expect(log, tests.values); }); } + +/// This allows a value of type T or T? to be treated as a value of type T?. +/// +/// We use this so that APIs that have become non-nullable can still be used +/// with `!` and `?` on the stable branch. +T? _ambiguate(T? value) => value; diff --git a/packages/google_sign_in/google_sign_in_platform_interface/test/method_channel_google_sign_in_test.dart b/packages/google_sign_in/google_sign_in_platform_interface/test/method_channel_google_sign_in_test.dart index 0972d0be4855..0837f6d5d02c 100644 --- a/packages/google_sign_in/google_sign_in_platform_interface/test/method_channel_google_sign_in_test.dart +++ b/packages/google_sign_in/google_sign_in_platform_interface/test/method_channel_google_sign_in_test.dart @@ -50,7 +50,9 @@ void main() { setUp(() { responses = Map.from(kDefaultResponses); - channel.setMockMethodCallHandler((MethodCall methodCall) { + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler(channel, (MethodCall methodCall) { log.add(methodCall); final dynamic response = responses[methodCall.method]; if (response != null && response is Exception) { @@ -160,3 +162,9 @@ void main() { }); }); } + +/// This allows a value of type T or T? to be treated as a value of type T?. +/// +/// We use this so that APIs that have become non-nullable can still be used +/// with `!` and `?` on the stable branch. +T? _ambiguate(T? value) => value; diff --git a/packages/image_picker/image_picker_android/test/image_picker_android_test.dart b/packages/image_picker/image_picker_android/test/image_picker_android_test.dart index ee1eb79f1045..d6680ce44dd5 100644 --- a/packages/image_picker/image_picker_android/test/image_picker_android_test.dart +++ b/packages/image_picker/image_picker_android/test/image_picker_android_test.dart @@ -18,7 +18,10 @@ void main() { setUp(() { returnValue = ''; - picker.channel.setMockMethodCallHandler((MethodCall methodCall) async { + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler(picker.channel, + (MethodCall methodCall) async { log.add(methodCall); return returnValue; }); @@ -189,7 +192,10 @@ void main() { }); test('handles a null image path response gracefully', () async { - picker.channel.setMockMethodCallHandler((MethodCall methodCall) => null); + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler( + picker.channel, (MethodCall methodCall) => null); expect(await picker.pickImage(source: ImageSource.gallery), isNull); expect(await picker.pickImage(source: ImageSource.camera), isNull); @@ -347,7 +353,10 @@ void main() { }); test('handles a null image path response gracefully', () async { - picker.channel.setMockMethodCallHandler((MethodCall methodCall) => null); + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler( + picker.channel, (MethodCall methodCall) => null); expect(await picker.pickMultiImage(), isNull); expect(await picker.pickMultiImage(), isNull); @@ -418,7 +427,10 @@ void main() { }); test('handles a null video path response gracefully', () async { - picker.channel.setMockMethodCallHandler((MethodCall methodCall) => null); + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler( + picker.channel, (MethodCall methodCall) => null); expect(await picker.pickVideo(source: ImageSource.gallery), isNull); expect(await picker.pickVideo(source: ImageSource.camera), isNull); @@ -460,7 +472,10 @@ void main() { group('#retrieveLostData', () { test('retrieveLostData get success response', () async { - picker.channel.setMockMethodCallHandler((MethodCall methodCall) async { + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler(picker.channel, + (MethodCall methodCall) async { return { 'type': 'image', 'path': '/example/path', @@ -473,7 +488,10 @@ void main() { }); test('retrieveLostData get error response', () async { - picker.channel.setMockMethodCallHandler((MethodCall methodCall) async { + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler(picker.channel, + (MethodCall methodCall) async { return { 'type': 'video', 'errorCode': 'test_error_code', @@ -488,14 +506,20 @@ void main() { }); test('retrieveLostData get null response', () async { - picker.channel.setMockMethodCallHandler((MethodCall methodCall) async { + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler(picker.channel, + (MethodCall methodCall) async { return null; }); expect((await picker.retrieveLostData()).isEmpty, true); }); test('retrieveLostData get both path and error should throw', () async { - picker.channel.setMockMethodCallHandler((MethodCall methodCall) async { + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler(picker.channel, + (MethodCall methodCall) async { return { 'type': 'video', 'errorCode': 'test_error_code', @@ -665,7 +689,10 @@ void main() { }); test('handles a null image path response gracefully', () async { - picker.channel.setMockMethodCallHandler((MethodCall methodCall) => null); + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler( + picker.channel, (MethodCall methodCall) => null); expect(await picker.getImage(source: ImageSource.gallery), isNull); expect(await picker.getImage(source: ImageSource.camera), isNull); @@ -823,7 +850,10 @@ void main() { }); test('handles a null image path response gracefully', () async { - picker.channel.setMockMethodCallHandler((MethodCall methodCall) => null); + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler( + picker.channel, (MethodCall methodCall) => null); expect(await picker.getMultiImage(), isNull); expect(await picker.getMultiImage(), isNull); @@ -894,7 +924,10 @@ void main() { }); test('handles a null video path response gracefully', () async { - picker.channel.setMockMethodCallHandler((MethodCall methodCall) => null); + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler( + picker.channel, (MethodCall methodCall) => null); expect(await picker.getVideo(source: ImageSource.gallery), isNull); expect(await picker.getVideo(source: ImageSource.camera), isNull); @@ -936,7 +969,10 @@ void main() { group('#getLostData', () { test('getLostData get success response', () async { - picker.channel.setMockMethodCallHandler((MethodCall methodCall) async { + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler(picker.channel, + (MethodCall methodCall) async { return { 'type': 'image', 'path': '/example/path', @@ -949,7 +985,10 @@ void main() { }); test('getLostData should successfully retrieve multiple files', () async { - picker.channel.setMockMethodCallHandler((MethodCall methodCall) async { + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler(picker.channel, + (MethodCall methodCall) async { return { 'type': 'image', 'path': '/example/path1', @@ -965,7 +1004,10 @@ void main() { }); test('getLostData get error response', () async { - picker.channel.setMockMethodCallHandler((MethodCall methodCall) async { + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler(picker.channel, + (MethodCall methodCall) async { return { 'type': 'video', 'errorCode': 'test_error_code', @@ -980,14 +1022,20 @@ void main() { }); test('getLostData get null response', () async { - picker.channel.setMockMethodCallHandler((MethodCall methodCall) async { + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler(picker.channel, + (MethodCall methodCall) async { return null; }); expect((await picker.getLostData()).isEmpty, true); }); test('getLostData get both path and error should throw', () async { - picker.channel.setMockMethodCallHandler((MethodCall methodCall) async { + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler(picker.channel, + (MethodCall methodCall) async { return { 'type': 'video', 'errorCode': 'test_error_code', @@ -1183,7 +1231,10 @@ void main() { }); test('handles a null image path response gracefully', () async { - picker.channel.setMockMethodCallHandler((MethodCall methodCall) => null); + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler( + picker.channel, (MethodCall methodCall) => null); expect( await picker.getImageFromSource(source: ImageSource.gallery), isNull); @@ -1254,3 +1305,9 @@ void main() { }); }); } + +/// This allows a value of type T or T? to be treated as a value of type T?. +/// +/// We use this so that APIs that have become non-nullable can still be used +/// with `!` and `?` on the stable branch. +T? _ambiguate(T? value) => value; diff --git a/packages/image_picker/image_picker_platform_interface/test/new_method_channel_image_picker_test.dart b/packages/image_picker/image_picker_platform_interface/test/new_method_channel_image_picker_test.dart index 44980100742a..244af3982672 100644 --- a/packages/image_picker/image_picker_platform_interface/test/new_method_channel_image_picker_test.dart +++ b/packages/image_picker/image_picker_platform_interface/test/new_method_channel_image_picker_test.dart @@ -18,7 +18,10 @@ void main() { setUp(() { returnValue = ''; - picker.channel.setMockMethodCallHandler((MethodCall methodCall) async { + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler(picker.channel, + (MethodCall methodCall) async { log.add(methodCall); return returnValue; }); @@ -185,8 +188,10 @@ void main() { }); test('handles a null image path response gracefully', () async { - picker.channel - .setMockMethodCallHandler((MethodCall methodCall) => null); + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler( + picker.channel, (MethodCall methodCall) => null); expect(await picker.pickImage(source: ImageSource.gallery), isNull); expect(await picker.pickImage(source: ImageSource.camera), isNull); @@ -352,8 +357,10 @@ void main() { }); test('handles a null image path response gracefully', () async { - picker.channel - .setMockMethodCallHandler((MethodCall methodCall) => null); + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler( + picker.channel, (MethodCall methodCall) => null); expect(await picker.pickMultiImage(), isNull); expect(await picker.pickMultiImage(), isNull); @@ -424,8 +431,10 @@ void main() { }); test('handles a null video path response gracefully', () async { - picker.channel - .setMockMethodCallHandler((MethodCall methodCall) => null); + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler( + picker.channel, (MethodCall methodCall) => null); expect(await picker.pickVideo(source: ImageSource.gallery), isNull); expect(await picker.pickVideo(source: ImageSource.camera), isNull); @@ -467,7 +476,10 @@ void main() { group('#retrieveLostData', () { test('retrieveLostData get success response', () async { - picker.channel.setMockMethodCallHandler((MethodCall methodCall) async { + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler(picker.channel, + (MethodCall methodCall) async { return { 'type': 'image', 'path': '/example/path', @@ -480,7 +492,10 @@ void main() { }); test('retrieveLostData get error response', () async { - picker.channel.setMockMethodCallHandler((MethodCall methodCall) async { + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler(picker.channel, + (MethodCall methodCall) async { return { 'type': 'video', 'errorCode': 'test_error_code', @@ -495,14 +510,20 @@ void main() { }); test('retrieveLostData get null response', () async { - picker.channel.setMockMethodCallHandler((MethodCall methodCall) async { + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler(picker.channel, + (MethodCall methodCall) async { return null; }); expect((await picker.retrieveLostData()).isEmpty, true); }); test('retrieveLostData get both path and error should throw', () async { - picker.channel.setMockMethodCallHandler((MethodCall methodCall) async { + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler(picker.channel, + (MethodCall methodCall) async { return { 'type': 'video', 'errorCode': 'test_error_code', @@ -672,8 +693,10 @@ void main() { }); test('handles a null image path response gracefully', () async { - picker.channel - .setMockMethodCallHandler((MethodCall methodCall) => null); + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler( + picker.channel, (MethodCall methodCall) => null); expect(await picker.getImage(source: ImageSource.gallery), isNull); expect(await picker.getImage(source: ImageSource.camera), isNull); @@ -839,8 +862,10 @@ void main() { }); test('handles a null image path response gracefully', () async { - picker.channel - .setMockMethodCallHandler((MethodCall methodCall) => null); + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler( + picker.channel, (MethodCall methodCall) => null); expect(await picker.getMultiImage(), isNull); expect(await picker.getMultiImage(), isNull); @@ -911,8 +936,10 @@ void main() { }); test('handles a null video path response gracefully', () async { - picker.channel - .setMockMethodCallHandler((MethodCall methodCall) => null); + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler( + picker.channel, (MethodCall methodCall) => null); expect(await picker.getVideo(source: ImageSource.gallery), isNull); expect(await picker.getVideo(source: ImageSource.camera), isNull); @@ -954,7 +981,10 @@ void main() { group('#getLostData', () { test('getLostData get success response', () async { - picker.channel.setMockMethodCallHandler((MethodCall methodCall) async { + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler(picker.channel, + (MethodCall methodCall) async { return { 'type': 'image', 'path': '/example/path', @@ -967,7 +997,10 @@ void main() { }); test('getLostData should successfully retrieve multiple files', () async { - picker.channel.setMockMethodCallHandler((MethodCall methodCall) async { + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler(picker.channel, + (MethodCall methodCall) async { return { 'type': 'image', 'path': '/example/path1', @@ -983,7 +1016,10 @@ void main() { }); test('getLostData get error response', () async { - picker.channel.setMockMethodCallHandler((MethodCall methodCall) async { + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler(picker.channel, + (MethodCall methodCall) async { return { 'type': 'video', 'errorCode': 'test_error_code', @@ -998,14 +1034,20 @@ void main() { }); test('getLostData get null response', () async { - picker.channel.setMockMethodCallHandler((MethodCall methodCall) async { + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler(picker.channel, + (MethodCall methodCall) async { return null; }); expect((await picker.getLostData()).isEmpty, true); }); test('getLostData get both path and error should throw', () async { - picker.channel.setMockMethodCallHandler((MethodCall methodCall) async { + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler(picker.channel, + (MethodCall methodCall) async { return { 'type': 'video', 'errorCode': 'test_error_code', @@ -1201,8 +1243,10 @@ void main() { }); test('handles a null image path response gracefully', () async { - picker.channel - .setMockMethodCallHandler((MethodCall methodCall) => null); + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler( + picker.channel, (MethodCall methodCall) => null); expect(await picker.getImageFromSource(source: ImageSource.gallery), isNull); @@ -1431,8 +1475,10 @@ void main() { }); test('handles a null image path response gracefully', () async { - picker.channel - .setMockMethodCallHandler((MethodCall methodCall) => null); + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler( + picker.channel, (MethodCall methodCall) => null); expect(await picker.getMultiImage(), isNull); expect(await picker.getMultiImage(), isNull); @@ -1478,3 +1524,9 @@ void main() { }); }); } + +/// This allows a value of type T or T? to be treated as a value of type T?. +/// +/// We use this so that APIs that have become non-nullable can still be used +/// with `!` and `?` on the stable branch. +T? _ambiguate(T? value) => value; diff --git a/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/billing_client_wrapper_test.dart b/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/billing_client_wrapper_test.dart index 4dae957e21eb..98219dc9d4e5 100644 --- a/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/billing_client_wrapper_test.dart +++ b/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/billing_client_wrapper_test.dart @@ -17,8 +17,9 @@ void main() { final StubInAppPurchasePlatform stubPlatform = StubInAppPurchasePlatform(); late BillingClient billingClient; - setUpAll(() => - channel.setMockMethodCallHandler(stubPlatform.fakeMethodCallHandler)); + setUpAll(() => _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler(channel, stubPlatform.fakeMethodCallHandler)); setUp(() { billingClient = BillingClient((PurchasesResultWrapper _) {}); @@ -651,3 +652,9 @@ void main() { }); }); } + +/// This allows a value of type T or T? to be treated as a value of type T?. +/// +/// We use this so that APIs that have become non-nullable can still be used +/// with `!` and `?` on the stable branch. +T? _ambiguate(T? value) => value; diff --git a/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_addition_test.dart b/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_addition_test.dart index 9737282e27b7..a97c69608a3a 100644 --- a/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_addition_test.dart +++ b/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_addition_test.dart @@ -22,7 +22,9 @@ void main() { const String endConnectionCall = 'BillingClient#endConnection()'; setUpAll(() { - channel.setMockMethodCallHandler(stubPlatform.fakeMethodCallHandler); + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler(channel, stubPlatform.fakeMethodCallHandler); }); setUp(() { @@ -213,3 +215,9 @@ void main() { }); }); } + +/// This allows a value of type T or T? to be treated as a value of type T?. +/// +/// We use this so that APIs that have become non-nullable can still be used +/// with `!` and `?` on the stable branch. +T? _ambiguate(T? value) => value; diff --git a/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_test.dart b/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_test.dart index 70e519ce9f6e..347bacde20b1 100644 --- a/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_test.dart +++ b/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_test.dart @@ -26,7 +26,9 @@ void main() { const String endConnectionCall = 'BillingClient#endConnection()'; setUpAll(() { - channel.setMockMethodCallHandler(stubPlatform.fakeMethodCallHandler); + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler(channel, stubPlatform.fakeMethodCallHandler); }); setUp(() { @@ -786,3 +788,9 @@ void main() { }); }); } + +/// This allows a value of type T or T? to be treated as a value of type T?. +/// +/// We use this so that APIs that have become non-nullable can still be used +/// with `!` and `?` on the stable branch. +T? _ambiguate(T? value) => value; diff --git a/packages/in_app_purchase/in_app_purchase_storekit/test/fakes/fake_storekit_platform.dart b/packages/in_app_purchase/in_app_purchase_storekit/test/fakes/fake_storekit_platform.dart index dfc715c86b4d..e6369161080f 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/test/fakes/fake_storekit_platform.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/test/fakes/fake_storekit_platform.dart @@ -14,7 +14,9 @@ import '../store_kit_wrappers/sk_test_stub_objects.dart'; class FakeStoreKitPlatform { FakeStoreKitPlatform() { - channel.setMockMethodCallHandler(onMethodCall); + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler(channel, onMethodCall); } // pre-configured store information @@ -235,3 +237,9 @@ class FakeStoreKitPlatform { return (call.arguments as Map).cast(); } } + +/// This allows a value of type T or T? to be treated as a value of type T?. +/// +/// We use this so that APIs that have become non-nullable can still be used +/// with `!` and `?` on the stable branch. +T? _ambiguate(T? value) => value; diff --git a/packages/in_app_purchase/in_app_purchase_storekit/test/in_app_purchase_storekit_platform_addtion_test.dart b/packages/in_app_purchase/in_app_purchase_storekit/test/in_app_purchase_storekit_platform_addtion_test.dart index dfdff5117091..2890e7542bbe 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/test/in_app_purchase_storekit_platform_addtion_test.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/test/in_app_purchase_storekit_platform_addtion_test.dart @@ -15,8 +15,10 @@ void main() { final FakeStoreKitPlatform fakeStoreKitPlatform = FakeStoreKitPlatform(); setUpAll(() { - SystemChannels.platform - .setMockMethodCallHandler(fakeStoreKitPlatform.onMethodCall); + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler( + SystemChannels.platform, fakeStoreKitPlatform.onMethodCall); }); group('present code redemption sheet', () { @@ -39,3 +41,9 @@ void main() { }); }); } + +/// This allows a value of type T or T? to be treated as a value of type T?. +/// +/// We use this so that APIs that have become non-nullable can still be used +/// with `!` and `?` on the stable branch. +T? _ambiguate(T? value) => value; diff --git a/packages/in_app_purchase/in_app_purchase_storekit/test/in_app_purchase_storekit_platform_test.dart b/packages/in_app_purchase/in_app_purchase_storekit/test/in_app_purchase_storekit_platform_test.dart index 51ff2c229483..fbb37974a208 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/test/in_app_purchase_storekit_platform_test.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/test/in_app_purchase_storekit_platform_test.dart @@ -21,8 +21,10 @@ void main() { late InAppPurchaseStoreKitPlatform iapStoreKitPlatform; setUpAll(() { - SystemChannels.platform - .setMockMethodCallHandler(fakeStoreKitPlatform.onMethodCall); + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler( + SystemChannels.platform, fakeStoreKitPlatform.onMethodCall); }); setUp(() { @@ -571,3 +573,9 @@ void main() { }); }); } + +/// This allows a value of type T or T? to be treated as a value of type T?. +/// +/// We use this so that APIs that have become non-nullable can still be used +/// with `!` and `?` on the stable branch. +T? _ambiguate(T? value) => value; diff --git a/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_methodchannel_apis_test.dart b/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_methodchannel_apis_test.dart index 4936f62a911a..0cf01b0bbfd6 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_methodchannel_apis_test.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_methodchannel_apis_test.dart @@ -14,8 +14,10 @@ void main() { final FakeStoreKitPlatform fakeStoreKitPlatform = FakeStoreKitPlatform(); setUpAll(() { - SystemChannels.platform - .setMockMethodCallHandler(fakeStoreKitPlatform.onMethodCall); + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler( + SystemChannels.platform, fakeStoreKitPlatform.onMethodCall); }); setUp(() {}); @@ -185,7 +187,9 @@ void main() { class FakeStoreKitPlatform { FakeStoreKitPlatform() { - channel.setMockMethodCallHandler(onMethodCall); + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler(channel, onMethodCall); } // get product request List startProductRequestParam = []; @@ -303,3 +307,9 @@ class TestPaymentTransactionObserver extends SKTransactionObserverWrapper { return true; } } + +/// This allows a value of type T or T? to be treated as a value of type T?. +/// +/// We use this so that APIs that have become non-nullable can still be used +/// with `!` and `?` on the stable branch. +T? _ambiguate(T? value) => value; diff --git a/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_payment_queue_delegate_api_test.dart b/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_payment_queue_delegate_api_test.dart index df76254aabf7..3d55fe27d7b0 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_payment_queue_delegate_api_test.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_payment_queue_delegate_api_test.dart @@ -13,8 +13,10 @@ void main() { final FakeStoreKitPlatform fakeStoreKitPlatform = FakeStoreKitPlatform(); setUpAll(() { - SystemChannels.platform - .setMockMethodCallHandler(fakeStoreKitPlatform.onMethodCall); + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler( + SystemChannels.platform, fakeStoreKitPlatform.onMethodCall); }); test( @@ -148,7 +150,9 @@ class TestPaymentQueueDelegate extends SKPaymentQueueDelegateWrapper { class FakeStoreKitPlatform { FakeStoreKitPlatform() { - channel.setMockMethodCallHandler(onMethodCall); + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler(channel, onMethodCall); } // indicate if the payment queue delegate is registered @@ -166,3 +170,9 @@ class FakeStoreKitPlatform { return Future.error('method not mocked'); } } + +/// This allows a value of type T or T? to be treated as a value of type T?. +/// +/// We use this so that APIs that have become non-nullable can still be used +/// with `!` and `?` on the stable branch. +T? _ambiguate(T? value) => value; diff --git a/packages/ios_platform_images/test/ios_platform_images_test.dart b/packages/ios_platform_images/test/ios_platform_images_test.dart index 76b012002dfa..f42b78646038 100644 --- a/packages/ios_platform_images/test/ios_platform_images_test.dart +++ b/packages/ios_platform_images/test/ios_platform_images_test.dart @@ -13,16 +13,26 @@ void main() { TestWidgetsFlutterBinding.ensureInitialized(); setUp(() { - channel.setMockMethodCallHandler((MethodCall methodCall) async { + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler(channel, (MethodCall methodCall) async { return '42'; }); }); tearDown(() { - channel.setMockMethodCallHandler(null); + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler(channel, null); }); test('resolveURL', () async { expect(await IosPlatformImages.resolveURL('foobar'), '42'); }); } + +/// This allows a value of type T or T? to be treated as a value of type T?. +/// +/// We use this so that APIs that have become non-nullable can still be used +/// with `!` and `?` on the stable branch. +T? _ambiguate(T? value) => value; diff --git a/packages/local_auth/local_auth_android/test/local_auth_test.dart b/packages/local_auth/local_auth_android/test/local_auth_test.dart index 86e5713f4bd6..136613d48245 100644 --- a/packages/local_auth/local_auth_android/test/local_auth_test.dart +++ b/packages/local_auth/local_auth_android/test/local_auth_test.dart @@ -18,7 +18,9 @@ void main() { late LocalAuthAndroid localAuthentication; setUp(() { - channel.setMockMethodCallHandler((MethodCall methodCall) { + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler(channel, (MethodCall methodCall) { log.add(methodCall); switch (methodCall.method) { case 'getEnrolledBiometrics': @@ -174,3 +176,9 @@ void main() { }); }); } + +/// This allows a value of type T or T? to be treated as a value of type T?. +/// +/// We use this so that APIs that have become non-nullable can still be used +/// with `!` and `?` on the stable branch. +T? _ambiguate(T? value) => value; diff --git a/packages/local_auth/local_auth_ios/test/local_auth_test.dart b/packages/local_auth/local_auth_ios/test/local_auth_test.dart index 0ad89e52f5ce..0d7f56d5da90 100644 --- a/packages/local_auth/local_auth_ios/test/local_auth_test.dart +++ b/packages/local_auth/local_auth_ios/test/local_auth_test.dart @@ -18,7 +18,9 @@ void main() { late LocalAuthIOS localAuthentication; setUp(() { - channel.setMockMethodCallHandler((MethodCall methodCall) { + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler(channel, (MethodCall methodCall) { log.add(methodCall); switch (methodCall.method) { case 'getEnrolledBiometrics': @@ -181,3 +183,9 @@ void main() { }); }); } + +/// This allows a value of type T or T? to be treated as a value of type T?. +/// +/// We use this so that APIs that have become non-nullable can still be used +/// with `!` and `?` on the stable branch. +T? _ambiguate(T? value) => value; diff --git a/packages/local_auth/local_auth_platform_interface/test/default_method_channel_platform_test.dart b/packages/local_auth/local_auth_platform_interface/test/default_method_channel_platform_test.dart index 824597ab2953..c513b4473574 100644 --- a/packages/local_auth/local_auth_platform_interface/test/default_method_channel_platform_test.dart +++ b/packages/local_auth/local_auth_platform_interface/test/default_method_channel_platform_test.dart @@ -29,7 +29,9 @@ void main() { }); test('getAvailableBiometrics', () async { - channel.setMockMethodCallHandler((MethodCall methodCall) { + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler(channel, (MethodCall methodCall) { log.add(methodCall); return Future.value([]); }); @@ -49,7 +51,9 @@ void main() { // existing unendorsed implementations, used 'undefined' as a special // return value from `getAvailableBiometrics` to indicate that nothing was // enrolled, but that the hardware does support biometrics. - channel.setMockMethodCallHandler((MethodCall methodCall) { + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler(channel, (MethodCall methodCall) { log.add(methodCall); return Future.value(['undefined']); }); @@ -68,7 +72,9 @@ void main() { group('Boolean returning methods', () { setUp(() { - channel.setMockMethodCallHandler((MethodCall methodCall) { + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler(channel, (MethodCall methodCall) { log.add(methodCall); return Future.value(true); }); @@ -198,3 +204,9 @@ void main() { }); }); } + +/// This allows a value of type T or T? to be treated as a value of type T?. +/// +/// We use this so that APIs that have become non-nullable can still be used +/// with `!` and `?` on the stable branch. +T? _ambiguate(T? value) => value; diff --git a/packages/path_provider/path_provider_platform_interface/test/method_channel_path_provider_test.dart b/packages/path_provider/path_provider_platform_interface/test/method_channel_path_provider_test.dart index 69c9b2b01f19..035e7becb9ff 100644 --- a/packages/path_provider/path_provider_platform_interface/test/method_channel_path_provider_test.dart +++ b/packages/path_provider/path_provider_platform_interface/test/method_channel_path_provider_test.dart @@ -25,8 +25,10 @@ void main() { setUp(() async { methodChannelPathProvider = MethodChannelPathProvider(); - methodChannelPathProvider.methodChannel - .setMockMethodCallHandler((MethodCall methodCall) async { + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler(methodChannelPathProvider.methodChannel, + (MethodCall methodCall) async { log.add(methodCall); switch (methodCall.method) { case 'getTemporaryDirectory': @@ -204,3 +206,9 @@ void main() { }); }); } + +/// This allows a value of type T or T? to be treated as a value of type T?. +/// +/// We use this so that APIs that have become non-nullable can still be used +/// with `!` and `?` on the stable branch. +T? _ambiguate(T? value) => value; diff --git a/packages/quick_actions/quick_actions_android/test/quick_actions_android_test.dart b/packages/quick_actions/quick_actions_android/test/quick_actions_android_test.dart index 40cfe458615d..0a98f5d4e55b 100644 --- a/packages/quick_actions/quick_actions_android/test/quick_actions_android_test.dart +++ b/packages/quick_actions/quick_actions_android/test/quick_actions_android_test.dart @@ -21,8 +21,10 @@ void main() { QuickActionsAndroid buildQuickActionsPlugin() { final QuickActionsAndroid quickActions = QuickActionsAndroid(); - quickActions.channel - .setMockMethodCallHandler((MethodCall methodCall) async { + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler(quickActions.channel, + (MethodCall methodCall) async { log.add(methodCall); return ''; }); @@ -162,3 +164,9 @@ void main() { }); }); } + +/// This allows a value of type T or T? to be treated as a value of type T?. +/// +/// We use this so that APIs that have become non-nullable can still be used +/// with `!` and `?` on the stable branch. +T? _ambiguate(T? value) => value; diff --git a/packages/quick_actions/quick_actions_ios/test/quick_actions_ios_test.dart b/packages/quick_actions/quick_actions_ios/test/quick_actions_ios_test.dart index 36827a5c6d8c..d2b062fff223 100644 --- a/packages/quick_actions/quick_actions_ios/test/quick_actions_ios_test.dart +++ b/packages/quick_actions/quick_actions_ios/test/quick_actions_ios_test.dart @@ -21,8 +21,10 @@ void main() { QuickActionsIos buildQuickActionsPlugin() { final QuickActionsIos quickActions = QuickActionsIos(); - quickActions.channel - .setMockMethodCallHandler((MethodCall methodCall) async { + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler(quickActions.channel, + (MethodCall methodCall) async { log.add(methodCall); return ''; }); @@ -162,3 +164,9 @@ void main() { }); }); } + +/// This allows a value of type T or T? to be treated as a value of type T?. +/// +/// We use this so that APIs that have become non-nullable can still be used +/// with `!` and `?` on the stable branch. +T? _ambiguate(T? value) => value; diff --git a/packages/quick_actions/quick_actions_platform_interface/test/method_channel_quick_actions_test.dart b/packages/quick_actions/quick_actions_platform_interface/test/method_channel_quick_actions_test.dart index 240f11bd8037..c1a508fbfb92 100644 --- a/packages/quick_actions/quick_actions_platform_interface/test/method_channel_quick_actions_test.dart +++ b/packages/quick_actions/quick_actions_platform_interface/test/method_channel_quick_actions_test.dart @@ -18,8 +18,10 @@ void main() { final List log = []; setUp(() { - quickActions.channel - .setMockMethodCallHandler((MethodCall methodCall) async { + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler(quickActions.channel, + (MethodCall methodCall) async { log.add(methodCall); return ''; }); @@ -148,3 +150,9 @@ void main() { }); }); } + +/// This allows a value of type T or T? to be treated as a value of type T?. +/// +/// We use this so that APIs that have become non-nullable can still be used +/// with `!` and `?` on the stable branch. +T? _ambiguate(T? value) => value; diff --git a/packages/shared_preferences/shared_preferences_android/test/shared_preferences_android_test.dart b/packages/shared_preferences/shared_preferences_android/test/shared_preferences_android_test.dart index fb6764893651..f1043daac1a4 100644 --- a/packages/shared_preferences/shared_preferences_android/test/shared_preferences_android_test.dart +++ b/packages/shared_preferences/shared_preferences_android/test/shared_preferences_android_test.dart @@ -38,7 +38,9 @@ void main() { .cast(); } - channel.setMockMethodCallHandler((MethodCall methodCall) async { + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler(channel, (MethodCall methodCall) async { log.add(methodCall); if (methodCall.method == 'getAll') { return testData.getAll(); @@ -124,3 +126,9 @@ void main() { }); }); } + +/// This allows a value of type T or T? to be treated as a value of type T?. +/// +/// We use this so that APIs that have become non-nullable can still be used +/// with `!` and `?` on the stable branch. +T? _ambiguate(T? value) => value; 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 index da333cf7f234..296592e70bb0 100644 --- 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 @@ -36,7 +36,9 @@ void main() { .cast(); } - channel.setMockMethodCallHandler((MethodCall methodCall) async { + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler(channel, (MethodCall methodCall) async { log.add(methodCall); if (methodCall.method == 'getAll') { return testData.getAll(); @@ -117,3 +119,9 @@ void main() { }); }); } + +/// This allows a value of type T or T? to be treated as a value of type T?. +/// +/// We use this so that APIs that have become non-nullable can still be used +/// with `!` and `?` on the stable branch. +T? _ambiguate(T? value) => value; diff --git a/packages/url_launcher/url_launcher_android/test/url_launcher_android_test.dart b/packages/url_launcher/url_launcher_android/test/url_launcher_android_test.dart index b8ccc2cbbee7..18db61e0b9fa 100644 --- a/packages/url_launcher/url_launcher_android/test/url_launcher_android_test.dart +++ b/packages/url_launcher/url_launcher_android/test/url_launcher_android_test.dart @@ -16,7 +16,9 @@ void main() { setUp(() { log = []; - channel.setMockMethodCallHandler((MethodCall methodCall) async { + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler(channel, (MethodCall methodCall) async { log.add(methodCall); // Return null explicitly instead of relying on the implicit null @@ -32,7 +34,9 @@ void main() { group('canLaunch', () { test('calls through', () async { - channel.setMockMethodCallHandler((MethodCall methodCall) async { + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler(channel, (MethodCall methodCall) async { log.add(methodCall); return true; }); @@ -59,7 +63,9 @@ void main() { test('checks a generic URL if an http URL returns false', () async { const String specificUrl = 'http://example.com/'; const String genericUrl = 'http://flutter.dev'; - channel.setMockMethodCallHandler((MethodCall methodCall) async { + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler(channel, (MethodCall methodCall) async { log.add(methodCall); return (methodCall.arguments as Map)['url'] != specificUrl; @@ -76,7 +82,9 @@ void main() { test('checks a generic URL if an https URL returns false', () async { const String specificUrl = 'https://example.com/'; const String genericUrl = 'https://flutter.dev'; - channel.setMockMethodCallHandler((MethodCall methodCall) async { + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler(channel, (MethodCall methodCall) async { log.add(methodCall); return (methodCall.arguments as Map)['url'] != specificUrl; @@ -91,7 +99,9 @@ void main() { }); test('does not a generic URL if a non-web URL returns false', () async { - channel.setMockMethodCallHandler((MethodCall methodCall) async { + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler(channel, (MethodCall methodCall) async { log.add(methodCall); return false; }); @@ -288,3 +298,9 @@ void main() { }); }); } + +/// This allows a value of type T or T? to be treated as a value of type T?. +/// +/// We use this so that APIs that have become non-nullable can still be used +/// with `!` and `?` on the stable branch. +T? _ambiguate(T? value) => value; diff --git a/packages/url_launcher/url_launcher_ios/test/url_launcher_ios_test.dart b/packages/url_launcher/url_launcher_ios/test/url_launcher_ios_test.dart index 8fad5807bddb..34dac1c4f925 100644 --- a/packages/url_launcher/url_launcher_ios/test/url_launcher_ios_test.dart +++ b/packages/url_launcher/url_launcher_ios/test/url_launcher_ios_test.dart @@ -14,7 +14,9 @@ void main() { const MethodChannel channel = MethodChannel('plugins.flutter.io/url_launcher_ios'); final List log = []; - channel.setMockMethodCallHandler((MethodCall methodCall) async { + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler(channel, (MethodCall methodCall) async { log.add(methodCall); // Return null explicitly instead of relying on the implicit null @@ -206,3 +208,9 @@ void main() { }); }); } + +/// This allows a value of type T or T? to be treated as a value of type T?. +/// +/// We use this so that APIs that have become non-nullable can still be used +/// with `!` and `?` on the stable branch. +T? _ambiguate(T? value) => value; diff --git a/packages/url_launcher/url_launcher_linux/test/url_launcher_linux_test.dart b/packages/url_launcher/url_launcher_linux/test/url_launcher_linux_test.dart index 7a4399dd4e6c..4e62cc446199 100644 --- a/packages/url_launcher/url_launcher_linux/test/url_launcher_linux_test.dart +++ b/packages/url_launcher/url_launcher_linux/test/url_launcher_linux_test.dart @@ -14,7 +14,9 @@ void main() { const MethodChannel channel = MethodChannel('plugins.flutter.io/url_launcher_linux'); final List log = []; - channel.setMockMethodCallHandler((MethodCall methodCall) async { + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler(channel, (MethodCall methodCall) async { log.add(methodCall); // Return null explicitly instead of relying on the implicit null @@ -142,3 +144,9 @@ void main() { }); }); } + +/// This allows a value of type T or T? to be treated as a value of type T?. +/// +/// We use this so that APIs that have become non-nullable can still be used +/// with `!` and `?` on the stable branch. +T? _ambiguate(T? value) => value; diff --git a/packages/url_launcher/url_launcher_macos/test/url_launcher_macos_test.dart b/packages/url_launcher/url_launcher_macos/test/url_launcher_macos_test.dart index 0a28aea678c3..26011fa6779a 100644 --- a/packages/url_launcher/url_launcher_macos/test/url_launcher_macos_test.dart +++ b/packages/url_launcher/url_launcher_macos/test/url_launcher_macos_test.dart @@ -14,7 +14,9 @@ void main() { const MethodChannel channel = MethodChannel('plugins.flutter.io/url_launcher_macos'); final List log = []; - channel.setMockMethodCallHandler((MethodCall methodCall) async { + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler(channel, (MethodCall methodCall) async { log.add(methodCall); // Return null explicitly instead of relying on the implicit null @@ -142,3 +144,9 @@ void main() { }); }); } + +/// This allows a value of type T or T? to be treated as a value of type T?. +/// +/// We use this so that APIs that have become non-nullable can still be used +/// with `!` and `?` on the stable branch. +T? _ambiguate(T? value) => value; diff --git a/packages/url_launcher/url_launcher_platform_interface/test/method_channel_url_launcher_test.dart b/packages/url_launcher/url_launcher_platform_interface/test/method_channel_url_launcher_test.dart index c8ec08c53095..9ccdd84ae890 100644 --- a/packages/url_launcher/url_launcher_platform_interface/test/method_channel_url_launcher_test.dart +++ b/packages/url_launcher/url_launcher_platform_interface/test/method_channel_url_launcher_test.dart @@ -48,7 +48,9 @@ void main() { const MethodChannel channel = MethodChannel('plugins.flutter.io/url_launcher'); final List log = []; - channel.setMockMethodCallHandler((MethodCall methodCall) async { + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler(channel, (MethodCall methodCall) async { log.add(methodCall); // Return null explicitly instead of relying on the implicit null @@ -323,3 +325,9 @@ class ExtendsUrlLauncherPlatform extends UrlLauncherPlatform { @override final LinkDelegate? linkDelegate = null; } + +/// This allows a value of type T or T? to be treated as a value of type T?. +/// +/// We use this so that APIs that have become non-nullable can still be used +/// with `!` and `?` on the stable branch. +T? _ambiguate(T? value) => value; diff --git a/packages/video_player/video_player_android/test/android_video_player_test.dart b/packages/video_player/video_player_android/test/android_video_player_test.dart index fa7ca7aa7f7a..6aa24e5c1808 100644 --- a/packages/video_player/video_player_android/test/android_video_player_test.dart +++ b/packages/video_player/video_player_android/test/android_video_player_test.dart @@ -234,16 +234,16 @@ void main() { }); test('videoEventsFor', () async { - _ambiguate(ServicesBinding.instance) - ?.defaultBinaryMessenger + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger .setMockMessageHandler( 'flutter.io/videoPlayer/videoEvents123', (ByteData? message) async { final MethodCall methodCall = const StandardMethodCodec().decodeMethodCall(message); if (methodCall.method == 'listen') { - await _ambiguate(ServicesBinding.instance) - ?.defaultBinaryMessenger + await _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger .handlePlatformMessage( 'flutter.io/videoPlayer/videoEvents123', const StandardMethodCodec() @@ -255,8 +255,8 @@ void main() { }), (ByteData? data) {}); - await _ambiguate(ServicesBinding.instance) - ?.defaultBinaryMessenger + await _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger .handlePlatformMessage( 'flutter.io/videoPlayer/videoEvents123', const StandardMethodCodec() @@ -269,8 +269,8 @@ void main() { }), (ByteData? data) {}); - await _ambiguate(ServicesBinding.instance) - ?.defaultBinaryMessenger + await _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger .handlePlatformMessage( 'flutter.io/videoPlayer/videoEvents123', const StandardMethodCodec() @@ -279,8 +279,8 @@ void main() { }), (ByteData? data) {}); - await _ambiguate(ServicesBinding.instance) - ?.defaultBinaryMessenger + await _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger .handlePlatformMessage( 'flutter.io/videoPlayer/videoEvents123', const StandardMethodCodec() @@ -293,8 +293,8 @@ void main() { }), (ByteData? data) {}); - await _ambiguate(ServicesBinding.instance) - ?.defaultBinaryMessenger + await _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger .handlePlatformMessage( 'flutter.io/videoPlayer/videoEvents123', const StandardMethodCodec() @@ -303,8 +303,8 @@ void main() { }), (ByteData? data) {}); - await _ambiguate(ServicesBinding.instance) - ?.defaultBinaryMessenger + await _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger .handlePlatformMessage( 'flutter.io/videoPlayer/videoEvents123', const StandardMethodCodec() diff --git a/packages/video_player/video_player_avfoundation/test/avfoundation_video_player_test.dart b/packages/video_player/video_player_avfoundation/test/avfoundation_video_player_test.dart index e7c3b5ba4ff3..c01373f05424 100644 --- a/packages/video_player/video_player_avfoundation/test/avfoundation_video_player_test.dart +++ b/packages/video_player/video_player_avfoundation/test/avfoundation_video_player_test.dart @@ -234,16 +234,16 @@ void main() { }); test('videoEventsFor', () async { - _ambiguate(ServicesBinding.instance) - ?.defaultBinaryMessenger + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger .setMockMessageHandler( 'flutter.io/videoPlayer/videoEvents123', (ByteData? message) async { final MethodCall methodCall = const StandardMethodCodec().decodeMethodCall(message); if (methodCall.method == 'listen') { - await _ambiguate(ServicesBinding.instance) - ?.defaultBinaryMessenger + await _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger .handlePlatformMessage( 'flutter.io/videoPlayer/videoEvents123', const StandardMethodCodec() @@ -255,8 +255,8 @@ void main() { }), (ByteData? data) {}); - await _ambiguate(ServicesBinding.instance) - ?.defaultBinaryMessenger + await _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger .handlePlatformMessage( 'flutter.io/videoPlayer/videoEvents123', const StandardMethodCodec() @@ -265,8 +265,8 @@ void main() { }), (ByteData? data) {}); - await _ambiguate(ServicesBinding.instance) - ?.defaultBinaryMessenger + await _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger .handlePlatformMessage( 'flutter.io/videoPlayer/videoEvents123', const StandardMethodCodec() @@ -279,8 +279,8 @@ void main() { }), (ByteData? data) {}); - await _ambiguate(ServicesBinding.instance) - ?.defaultBinaryMessenger + await _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger .handlePlatformMessage( 'flutter.io/videoPlayer/videoEvents123', const StandardMethodCodec() @@ -289,8 +289,8 @@ void main() { }), (ByteData? data) {}); - await _ambiguate(ServicesBinding.instance) - ?.defaultBinaryMessenger + await _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger .handlePlatformMessage( 'flutter.io/videoPlayer/videoEvents123', const StandardMethodCodec() diff --git a/packages/webview_flutter/webview_flutter_android/test/legacy/surface_android_test.dart b/packages/webview_flutter/webview_flutter_android/test/legacy/surface_android_test.dart index 196e7cc27093..d022ab282c92 100644 --- a/packages/webview_flutter/webview_flutter_android/test/legacy/surface_android_test.dart +++ b/packages/webview_flutter/webview_flutter_android/test/legacy/surface_android_test.dart @@ -17,7 +17,10 @@ void main() { late List log; setUpAll(() { - SystemChannels.platform_views.setMockMethodCallHandler( + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler( + SystemChannels.platform_views, (MethodCall call) async { log.add(call); if (call.method == 'resize') { @@ -29,12 +32,15 @@ void main() { 'height': arguments['height'], }; } + return null; }, ); }); tearDownAll(() { - SystemChannels.platform_views.setMockMethodCallHandler(null); + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler(SystemChannels.platform_views, null); }); setUp(() { @@ -116,3 +122,9 @@ class TestWebViewPlatformCallbacksHandler @override void onWebResourceError(WebResourceError error) {} } + +/// This allows a value of type T or T? to be treated as a value of type T?. +/// +/// We use this so that APIs that have become non-nullable can still be used +/// with `!` and `?` on the stable branch. +T? _ambiguate(T? value) => value; From 190c6d9164d2af80d168ad5bcb8aeafff4284a7c Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Sat, 18 Feb 2023 13:13:07 -0500 Subject: [PATCH 115/130] Roll Flutter from 298d8c76ba78 to 0be7c3f30d64 (21 revisions) (#7194) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 674254c03 Always use the testbed in web_test.dart so `environment` is populated. (flutter/flutter#120984) * c4d40cc15 Modify the updateChildren method deep copy _children (flutter/flutter#120773) * 9367641ce clean up (flutter/flutter#120934) * 51712b90a Roll Plugins from d699b4a91381 to 8f3419be5e0e (7 revisions) (flutter/flutter#120993) * c3587c62e Add `InheritedTheme` support to `ScrollbarTheme` (flutter/flutter#120970) * 08b409ab0 Roll Flutter Engine from 6e92c0c28410 to bd37a3992b50 (16 revisions) (flutter/flutter#121004) * f78513685 [web] Temporarily disable a line boundary test (flutter/flutter#121005) * 9fe556705 Print sub process that failed to run in tool (flutter/flutter#120999) * 6205c110d Remove "note that" in our documentation (as per style guide) (flutter/flutter#120842) * 1daa0be4f Fix scrollable to clear inner semantics node if it does not use two p… (flutter/flutter#120996) * 7f19b7485 0a27673d7 Roll Skia from 02890036028e to 0e444e355607 (9 revisions) (flutter/engine#39723) (flutter/flutter#121008) * 48d2dfc72 e7fde3f72 [web] Make glassPaneElement and glassPaneShadow non-nullable (flutter/engine#39692) (flutter/flutter#121009) * 610450523 2b2780185 Roll Skia from 0e444e355607 to 4b79e398dfe0 (5 revisions) (flutter/engine#39725) (flutter/flutter#121016) * f99f47280 Remove the deprecated accentColor from ThemeData (flutter/flutter#120932) * 2b4c96088 Remove more references to dart:ui.window (flutter/flutter#120994) * 0fa652752 Roll Flutter Engine from 2b2780185dd5 to a37e27b77008 (2 revisions) (flutter/flutter#121020) * 9281114fb Roll Flutter Engine from a37e27b77008 to 2fdce9a96367 (2 revisions) (flutter/flutter#121023) * 4dd555d32 Roll Flutter Engine from 2fdce9a96367 to 9a3c3e462fce (3 revisions) (flutter/flutter#121025) * 66dce657f Roll Flutter Engine from 9a3c3e462fce to 3777ed51774f (2 revisions) (flutter/flutter#121029) * a5b53a6d2 a9db42c3e Roll Skia from 733a19f6a625 to 2f05923f825e (3 revisions) (flutter/engine#39744) (flutter/flutter#121030) * 0be7c3f30 Roll Flutter Engine from a9db42c3edc2 to c22c64812243 (2 revisions) (flutter/flutter#121041) --- .ci/flutter_master.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/flutter_master.version b/.ci/flutter_master.version index b0f7e4c85b98..b2177178bc38 100644 --- a/.ci/flutter_master.version +++ b/.ci/flutter_master.version @@ -1 +1 @@ -298d8c76ba78007deb5b96f320a11ccefe97a794 +0be7c3f30d647423e3bec24ebbb5f89e03e79b96 From c0d4e8041626baf564bba59a1370a4b77e5930d8 Mon Sep 17 00:00:00 2001 From: David Iglesias Date: Sun, 19 Feb 2023 05:34:26 -0800 Subject: [PATCH 116/130] [google_sign_in] Endorses next web package. (#7191) Bump major version to 6.0.0. --- packages/google_sign_in/google_sign_in/CHANGELOG.md | 12 ++++++++++++ packages/google_sign_in/google_sign_in/pubspec.yaml | 4 ++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/google_sign_in/google_sign_in/CHANGELOG.md b/packages/google_sign_in/google_sign_in/CHANGELOG.md index 8888253313ba..f6b1e5790cc4 100644 --- a/packages/google_sign_in/google_sign_in/CHANGELOG.md +++ b/packages/google_sign_in/google_sign_in/CHANGELOG.md @@ -1,3 +1,15 @@ +## 6.0.0 + +* **Breaking change** for platform `web`: + * Endorses `google_sign_in_web: ^0.11.0` as the web implementation of the plugin. + * The web package is now backed by the **Google Identity Services (GIS) SDK**, + instead of the **Google Sign-In for Web JS SDK**, which is set to be deprecated + after March 31, 2023. + * Migration information can be found in the + [`google_sign_in_web` package README](https://pub.dev/packages/google_sign_in_web). + +For every platform other than `web`, this version should be identical to `5.4.4`. + ## 5.4.4 * Adds documentation for iOS auth with SERVER_CLIENT_ID diff --git a/packages/google_sign_in/google_sign_in/pubspec.yaml b/packages/google_sign_in/google_sign_in/pubspec.yaml index 056700284075..ec61a31598d7 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. repository: https://github.com/flutter/plugins/tree/main/packages/google_sign_in/google_sign_in issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+google_sign_in%22 -version: 5.4.4 +version: 6.0.0 environment: sdk: ">=2.14.0 <3.0.0" @@ -25,7 +25,7 @@ dependencies: google_sign_in_android: ^6.1.0 google_sign_in_ios: ^5.5.0 google_sign_in_platform_interface: ^2.2.0 - google_sign_in_web: ^0.10.0 + google_sign_in_web: ^0.11.0 dev_dependencies: build_runner: ^2.1.10 From cc4eaba0fcea8d3f6acfde73cac16ac97c73d5a4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 19 Feb 2023 09:31:40 -0500 Subject: [PATCH 117/130] [google_maps]: Bump org.mockito:mockito-core (#7099) Bumps [org.mockito:mockito-core](https://github.com/mockito/mockito) from 4.7.0 to 5.1.1. - [Release notes](https://github.com/mockito/mockito/releases) - [Commits](https://github.com/mockito/mockito/compare/v4.7.0...v5.1.1) --- updated-dependencies: - dependency-name: org.mockito:mockito-core dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../google_maps_flutter_android/android/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google_maps_flutter/google_maps_flutter_android/android/build.gradle b/packages/google_maps_flutter/google_maps_flutter_android/android/build.gradle index 5b383fe3bc86..79486a2716a2 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/android/build.gradle +++ b/packages/google_maps_flutter/google_maps_flutter_android/android/build.gradle @@ -42,7 +42,7 @@ android { androidTestImplementation 'androidx.test:rules:1.4.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' testImplementation 'junit:junit:4.13.2' - testImplementation 'org.mockito:mockito-core:4.7.0' + testImplementation 'org.mockito:mockito-core:5.1.1' testImplementation 'androidx.test:core:1.2.0' testImplementation "org.robolectric:robolectric:4.3.1" } From 717a8bfef3f25b5cc0a37cae9d04cdfd13d1a3b0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 19 Feb 2023 14:55:43 +0000 Subject: [PATCH 118/130] [image_picker]: Bump org.mockito:mockito-core (#7097) Bumps [org.mockito:mockito-core](https://github.com/mockito/mockito) from 4.8.0 to 5.1.1. - [Release notes](https://github.com/mockito/mockito/releases) - [Commits](https://github.com/mockito/mockito/compare/v4.8.0...v5.1.1) --- updated-dependencies: - dependency-name: org.mockito:mockito-core dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- packages/image_picker/image_picker_android/android/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/image_picker/image_picker_android/android/build.gradle b/packages/image_picker/image_picker_android/android/build.gradle index aed1ad5174ea..145ee96ca19e 100644 --- a/packages/image_picker/image_picker_android/android/build.gradle +++ b/packages/image_picker/image_picker_android/android/build.gradle @@ -39,7 +39,7 @@ android { implementation 'androidx.exifinterface:exifinterface:1.3.3' testImplementation 'junit:junit:4.13.2' - testImplementation 'org.mockito:mockito-core:4.8.0' + testImplementation 'org.mockito:mockito-core:5.1.1' testImplementation 'androidx.test:core:1.4.0' testImplementation "org.robolectric:robolectric:4.8.1" } From 8a09d8c130d418813351d7882682a50e0af958d8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 19 Feb 2023 14:55:45 +0000 Subject: [PATCH 119/130] [lifecycle]: Bump org.mockito:mockito-core (#7096) Bumps [org.mockito:mockito-core](https://github.com/mockito/mockito) from 4.7.0 to 5.1.1. - [Release notes](https://github.com/mockito/mockito/releases) - [Commits](https://github.com/mockito/mockito/compare/v4.7.0...v5.1.1) --- updated-dependencies: - dependency-name: org.mockito:mockito-core dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- packages/flutter_plugin_android_lifecycle/android/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/flutter_plugin_android_lifecycle/android/build.gradle b/packages/flutter_plugin_android_lifecycle/android/build.gradle index 5786a74e2e78..ae7e518b69da 100644 --- a/packages/flutter_plugin_android_lifecycle/android/build.gradle +++ b/packages/flutter_plugin_android_lifecycle/android/build.gradle @@ -56,6 +56,6 @@ android { dependencies { testImplementation 'junit:junit:4.13.2' - testImplementation 'org.mockito:mockito-core:4.7.0' + testImplementation 'org.mockito:mockito-core:5.1.1' } From 40377a12a7f12d23c1a7d869e49eaff1a4055226 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 19 Feb 2023 14:55:47 +0000 Subject: [PATCH 120/130] [in_app_pur]: Bump org.mockito:mockito-core (#7094) Bumps [org.mockito:mockito-core](https://github.com/mockito/mockito) from 4.7.0 to 5.1.1. - [Release notes](https://github.com/mockito/mockito/releases) - [Commits](https://github.com/mockito/mockito/compare/v4.7.0...v5.1.1) --- updated-dependencies: - dependency-name: org.mockito:mockito-core dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../in_app_purchase_android/example/android/app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/in_app_purchase/in_app_purchase_android/example/android/app/build.gradle b/packages/in_app_purchase/in_app_purchase_android/example/android/app/build.gradle index 281f349989be..511091df086d 100644 --- a/packages/in_app_purchase/in_app_purchase_android/example/android/app/build.gradle +++ b/packages/in_app_purchase/in_app_purchase_android/example/android/app/build.gradle @@ -108,7 +108,7 @@ flutter { dependencies { implementation 'com.android.billingclient:billing:5.0.0' testImplementation 'junit:junit:4.13.2' - testImplementation 'org.mockito:mockito-core:4.7.0' + testImplementation 'org.mockito:mockito-core:5.1.1' testImplementation 'org.json:json:20220924' androidTestImplementation 'androidx.test:runner:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' From 6a4bbf1df4f977c6e7530976a8fc2c3a3b524cde Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 19 Feb 2023 15:15:09 +0000 Subject: [PATCH 121/130] [url_launcher]: Bump org.mockito:mockito-core (#7098) Bumps [org.mockito:mockito-core](https://github.com/mockito/mockito) from 4.8.0 to 5.1.1. - [Release notes](https://github.com/mockito/mockito/releases) - [Commits](https://github.com/mockito/mockito/compare/v4.8.0...v5.1.1) --- updated-dependencies: - dependency-name: org.mockito:mockito-core dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- packages/url_launcher/url_launcher_android/android/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/url_launcher/url_launcher_android/android/build.gradle b/packages/url_launcher/url_launcher_android/android/build.gradle index dbd68d99c1a2..b3d103c32081 100644 --- a/packages/url_launcher/url_launcher_android/android/build.gradle +++ b/packages/url_launcher/url_launcher_android/android/build.gradle @@ -51,7 +51,7 @@ android { dependencies { compileOnly 'androidx.annotation:annotation:1.2.0' testImplementation 'junit:junit:4.13.2' - testImplementation 'org.mockito:mockito-core:4.8.0' + testImplementation 'org.mockito:mockito-core:5.1.1' testImplementation 'androidx.test:core:1.0.0' testImplementation 'org.robolectric:robolectric:4.3' } From 96ab5cd1218820d2aaecf822af7b2dc57595a87f Mon Sep 17 00:00:00 2001 From: Camille Simon <43054281+camsim99@users.noreply.github.com> Date: Sun, 19 Feb 2023 08:01:16 -0800 Subject: [PATCH 122/130] Update codeowners (#7188) --- CODEOWNERS | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 86c13b4502ff..2df19233dd9a 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -29,16 +29,16 @@ packages/**/*_web/** @ditman # - Android packages/camera/camera_android/** @camsim99 packages/camera/camera_android_camerax/** @camsim99 -packages/espresso/** @GaryQian -packages/flutter_plugin_android_lifecycle/** @GaryQian -packages/google_maps_flutter/google_maps_flutter_android/** @GaryQian +packages/espresso/** @reidbaker +packages/flutter_plugin_android_lifecycle/** @reidbaker +packages/google_maps_flutter/google_maps_flutter_android/** @reidbaker packages/google_sign_in/google_sign_in_android/** @camsim99 -packages/image_picker/image_picker_android/** @GaryQian -packages/in_app_purchase/in_app_purchase_android/** @GaryQian +packages/image_picker/image_picker_android/** @gmackall +packages/in_app_purchase/in_app_purchase_android/** @gmackall packages/local_auth/local_auth_android/** @camsim99 packages/path_provider/path_provider_android/** @camsim99 packages/quick_actions/quick_actions_android/** @camsim99 -packages/url_launcher/url_launcher_android/** @GaryQian +packages/url_launcher/url_launcher_android/** @gmackall packages/video_player/video_player_android/** @camsim99 # - iOS From 00d5855cc9a5d547f44ab69b5461b2c2a8ffb94a Mon Sep 17 00:00:00 2001 From: stuartmorgan Date: Sun, 19 Feb 2023 08:03:43 -0800 Subject: [PATCH 123/130] Add missing CODEOWNER (#7016) `shared_preferences_android` was accidentally missing a code owner. --- CODEOWNERS | 1 + 1 file changed, 1 insertion(+) diff --git a/CODEOWNERS b/CODEOWNERS index 2df19233dd9a..603e4a24fcc0 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -38,6 +38,7 @@ packages/in_app_purchase/in_app_purchase_android/** @gmackall packages/local_auth/local_auth_android/** @camsim99 packages/path_provider/path_provider_android/** @camsim99 packages/quick_actions/quick_actions_android/** @camsim99 +packages/shared_preferences/shared_preferences_android/** @reidbaker packages/url_launcher/url_launcher_android/** @gmackall packages/video_player/video_player_android/** @camsim99 From c3e9d1ba36173cd93b96ec7ccfb4ac23c8b8f392 Mon Sep 17 00:00:00 2001 From: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> Date: Sun, 19 Feb 2023 11:04:23 -0500 Subject: [PATCH 124/130] [webview_flutter] Adds examples of accessing platform-specific features for each class (#7089) * start * more docs and version bump * fix main --- .../webview_flutter/CHANGELOG.md | 4 ++ .../webview_flutter/webview_flutter/README.md | 12 +++- .../lib/src/navigation_delegate.dart | 49 +++++++++++++++ .../lib/src/webview_controller.dart | 56 +++++++++++++++++ .../lib/src/webview_cookie_manager.dart | 52 +++++++++++++++ .../lib/src/webview_widget.dart | 63 ++++++++++++++++++- .../webview_flutter/pubspec.yaml | 2 +- 7 files changed, 232 insertions(+), 6 deletions(-) diff --git a/packages/webview_flutter/webview_flutter/CHANGELOG.md b/packages/webview_flutter/webview_flutter/CHANGELOG.md index 6d2e860e29ec..84f890790128 100644 --- a/packages/webview_flutter/webview_flutter/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter/CHANGELOG.md @@ -1,3 +1,7 @@ +## 4.0.4 + +* Adds examples of accessing platform-specific features for each class. + ## 4.0.3 * Updates example code for `use_build_context_synchronously` lint. diff --git a/packages/webview_flutter/webview_flutter/README.md b/packages/webview_flutter/webview_flutter/README.md index 98f7b667025b..b30b8bc20fa1 100644 --- a/packages/webview_flutter/webview_flutter/README.md +++ b/packages/webview_flutter/webview_flutter/README.md @@ -99,7 +99,7 @@ import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart'; ``` Now, additional features can be accessed through the platform implementations. Classes -`WebViewController`, `WebViewWidget`, `NavigationDelegate`, and `WebViewCookieManager` pass their +[WebViewController], [WebViewWidget], [NavigationDelegate], and [WebViewCookieManager] pass their functionality to a class provided by the current platform. Below are a couple of ways to access additional functionality provided by the platform and is followed by an example. @@ -212,11 +212,17 @@ Below is a non-exhaustive list of changes to the API: * `WebView.userAgent` -> `WebViewController.setUserAgent` * `WebView.backgroundColor` -> `WebViewController.setBackgroundColor` * The following features have been moved to an Android implementation class. See section - `Platform-Specific Features` for details on accessing Android platform specific features. + `Platform-Specific Features` for details on accessing Android platform-specific features. * `WebView.debuggingEnabled` -> `static AndroidWebViewController.enableDebugging` * `WebView.initialMediaPlaybackPolicy` -> `AndroidWebViewController.setMediaPlaybackRequiresUserGesture` * The following features have been moved to an iOS implementation class. See section - `Platform-Specific Features` for details on accessing iOS platform specific features. + `Platform-Specific Features` for details on accessing iOS platform-specific features. * `WebView.gestureNavigationEnabled` -> `WebKitWebViewController.setAllowsBackForwardNavigationGestures` * `WebView.initialMediaPlaybackPolicy` -> `WebKitWebViewControllerCreationParams.mediaTypesRequiringUserAction` * `WebView.allowsInlineMediaPlayback` -> `WebKitWebViewControllerCreationParams.allowsInlineMediaPlayback` + + +[WebViewController]: https://pub.dev/documentation/webview_flutter/latest/webview_flutter/WebViewController-class.html +[WebViewWidget]: https://pub.dev/documentation/webview_flutter/latest/webview_flutter/WebViewWidget-class.html +[NavigationDelegate]: https://pub.dev/documentation/webview_flutter/latest/webview_flutter/NavigationDelegate-class.html +[WebViewCookieManager]: https://pub.dev/documentation/webview_flutter/latest/webview_flutter/WebViewCookieManager-class.html \ No newline at end of file diff --git a/packages/webview_flutter/webview_flutter/lib/src/navigation_delegate.dart b/packages/webview_flutter/webview_flutter/lib/src/navigation_delegate.dart index 0651ad45f229..3237fa41c0bb 100644 --- a/packages/webview_flutter/webview_flutter/lib/src/navigation_delegate.dart +++ b/packages/webview_flutter/webview_flutter/lib/src/navigation_delegate.dart @@ -12,6 +12,28 @@ import 'webview_controller.dart'; /// the progress of navigation requests. /// /// See [WebViewController.setNavigationDelegate]. +/// +/// ## Platform-Specific Features +/// This class contains an underlying implementation provided by the current +/// platform. Once a platform implementation is imported, the examples below +/// can be followed to use features provided by a platform's implementation. +/// +/// {@macro webview_flutter.NavigationDelegate.fromPlatformCreationParams} +/// +/// Below is an example of accessing the platform-specific implementation for +/// iOS and Android: +/// +/// ```dart +/// final NavigationDelegate navigationDelegate = NavigationDelegate(); +/// +/// if (WebViewPlatform.instance is WebKitWebViewPlatform) { +/// final WebKitNavigationDelegate webKitDelegate = +/// navigationDelegate.platform as WebKitNavigationDelegate; +/// } else if (WebViewPlatform.instance is AndroidWebViewPlatform) { +/// final AndroidNavigationDelegate androidDelegate = +/// navigationDelegate.platform as AndroidNavigationDelegate; +/// } +/// ``` class NavigationDelegate { /// Constructs a [NavigationDelegate]. NavigationDelegate({ @@ -32,6 +54,33 @@ class NavigationDelegate { /// Constructs a [NavigationDelegate] from creation params for a specific /// platform. + /// + /// {@template webview_flutter.NavigationDelegate.fromPlatformCreationParams} + /// Below is an example of setting platform-specific creation parameters for + /// iOS and Android: + /// + /// ```dart + /// PlatformNavigationDelegateCreationParams params = + /// const PlatformNavigationDelegateCreationParams(); + /// + /// if (WebViewPlatform.instance is WebKitWebViewPlatform) { + /// params = WebKitNavigationDelegateCreationParams + /// .fromPlatformNavigationDelegateCreationParams( + /// params, + /// ); + /// } else if (WebViewPlatform.instance is AndroidWebViewPlatform) { + /// params = AndroidNavigationDelegateCreationParams + /// .fromPlatformNavigationDelegateCreationParams( + /// params, + /// ); + /// } + /// + /// final NavigationDelegate navigationDelegate = + /// NavigationDelegate.fromPlatformCreationParams( + /// params, + /// ); + /// ``` + /// {@endtemplate} NavigationDelegate.fromPlatformCreationParams( PlatformNavigationDelegateCreationParams params, { FutureOr Function(NavigationRequest request)? diff --git a/packages/webview_flutter/webview_flutter/lib/src/webview_controller.dart b/packages/webview_flutter/webview_flutter/lib/src/webview_controller.dart index d632d1e95231..a112f1522579 100644 --- a/packages/webview_flutter/webview_flutter/lib/src/webview_controller.dart +++ b/packages/webview_flutter/webview_flutter/lib/src/webview_controller.dart @@ -10,12 +10,41 @@ import 'package:flutter/material.dart'; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; import 'navigation_delegate.dart'; +import 'webview_widget.dart'; /// Controls a WebView provided by the host platform. /// /// Pass this to a [WebViewWidget] to display the WebView. +/// +/// A [WebViewController] can only be used by a single [WebViewWidget] at a +/// time. +/// +/// ## Platform-Specific Features +/// This class contains an underlying implementation provided by the current +/// platform. Once a platform implementation is imported, the examples below +/// can be followed to use features provided by a platform's implementation. +/// +/// {@macro webview_flutter.WebViewController.fromPlatformCreationParams} +/// +/// Below is an example of accessing the platform-specific implementation for +/// iOS and Android: +/// +/// ```dart +/// final WebViewController webViewController = WebViewController(); +/// +/// if (WebViewPlatform.instance is WebKitWebViewPlatform) { +/// final WebKitWebViewController webKitController = +/// webViewController.platform as WebKitWebViewController; +/// } else if (WebViewPlatform.instance is AndroidWebViewPlatform) { +/// final AndroidWebViewController androidController = +/// webViewController.platform as AndroidWebViewController; +/// } +/// ``` class WebViewController { /// Constructs a [WebViewController]. + /// + /// See [WebViewController.fromPlatformCreationParams] for setting parameters + /// for a specific platform. WebViewController() : this.fromPlatformCreationParams( const PlatformWebViewControllerCreationParams(), @@ -23,6 +52,33 @@ class WebViewController { /// Constructs a [WebViewController] from creation params for a specific /// platform. + /// + /// {@template webview_flutter.WebViewCookieManager.fromPlatformCreationParams} + /// Below is an example of setting platform-specific creation parameters for + /// iOS and Android: + /// + /// ```dart + /// PlatformWebViewControllerCreationParams params = + /// const PlatformWebViewControllerCreationParams(); + /// + /// if (WebViewPlatform.instance is WebKitWebViewPlatform) { + /// params = WebKitWebViewControllerCreationParams + /// .fromPlatformWebViewControllerCreationParams( + /// params, + /// ); + /// } else if (WebViewPlatform.instance is AndroidWebViewPlatform) { + /// params = AndroidWebViewControllerCreationParams + /// .fromPlatformWebViewControllerCreationParams( + /// params, + /// ); + /// } + /// + /// final WebViewController webViewController = + /// WebViewController.fromPlatformCreationParams( + /// params, + /// ); + /// ``` + /// {@endtemplate} WebViewController.fromPlatformCreationParams( PlatformWebViewControllerCreationParams params, ) : this.fromPlatform(PlatformWebViewController(params)); diff --git a/packages/webview_flutter/webview_flutter/lib/src/webview_cookie_manager.dart b/packages/webview_flutter/webview_flutter/lib/src/webview_cookie_manager.dart index bffa1b5a71d2..353d7554fcb2 100644 --- a/packages/webview_flutter/webview_flutter/lib/src/webview_cookie_manager.dart +++ b/packages/webview_flutter/webview_flutter/lib/src/webview_cookie_manager.dart @@ -5,8 +5,33 @@ import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; /// Manages cookies pertaining to all WebViews. +/// +/// ## Platform-Specific Features +/// This class contains an underlying implementation provided by the current +/// platform. Once a platform implementation is imported, the examples below +/// can be followed to use features provided by a platform's implementation. +/// +/// {@macro webview_flutter.WebViewCookieManager.fromPlatformCreationParams} +/// +/// Below is an example of accessing the platform-specific implementation for +/// iOS and Android: +/// +/// ```dart +/// final WebViewCookieManager cookieManager = WebViewCookieManager(); +/// +/// if (WebViewPlatform.instance is WebKitWebViewPlatform) { +/// final WebKitWebViewCookieManager webKitManager = +/// cookieManager.platform as WebKitWebViewCookieManager; +/// } else if (WebViewPlatform.instance is AndroidWebViewPlatform) { +/// final AndroidWebViewCookieManager androidManager = +/// cookieManager.platform as AndroidWebViewCookieManager; +/// } +/// ``` class WebViewCookieManager { /// Constructs a [WebViewCookieManager]. + /// + /// See [WebViewCookieManager.fromPlatformCreationParams] for setting + /// parameters for a specific platform. WebViewCookieManager() : this.fromPlatformCreationParams( const PlatformWebViewCookieManagerCreationParams(), @@ -14,6 +39,33 @@ class WebViewCookieManager { /// Constructs a [WebViewCookieManager] from creation params for a specific /// platform. + /// + /// {@template webview_flutter.WebViewCookieManager.fromPlatformCreationParams} + /// Below is an example of setting platform-specific creation parameters for + /// iOS and Android: + /// + /// ```dart + /// PlatformWebViewCookieManagerCreationParams params = + /// const PlatformWebViewCookieManagerCreationParams(); + /// + /// if (WebViewPlatform.instance is WebKitWebViewPlatform) { + /// params = WebKitWebViewCookieManagerCreationParams + /// .fromPlatformWebViewCookieManagerCreationParams( + /// params, + /// ); + /// } else if (WebViewPlatform.instance is AndroidWebViewPlatform) { + /// params = AndroidWebViewCookieManagerCreationParams + /// .fromPlatformWebViewCookieManagerCreationParams( + /// params, + /// ); + /// } + /// + /// final WebViewCookieManager webViewCookieManager = + /// WebViewCookieManager.fromPlatformCreationParams( + /// params, + /// ); + /// ``` + /// {@endtemplate} WebViewCookieManager.fromPlatformCreationParams( PlatformWebViewCookieManagerCreationParams params, ) : this.fromPlatform(PlatformWebViewCookieManager(params)); diff --git a/packages/webview_flutter/webview_flutter/lib/src/webview_widget.dart b/packages/webview_flutter/webview_flutter/lib/src/webview_widget.dart index b3180115c801..440d0f6654ec 100644 --- a/packages/webview_flutter/webview_flutter/lib/src/webview_widget.dart +++ b/packages/webview_flutter/webview_flutter/lib/src/webview_widget.dart @@ -10,8 +10,35 @@ import 'package:webview_flutter_platform_interface/webview_flutter_platform_inte import 'webview_controller.dart'; /// Displays a native WebView as a Widget. +/// +/// ## Platform-Specific Features +/// This class contains an underlying implementation provided by the current +/// platform. Once a platform implementation is imported, the examples below +/// can be followed to use features provided by a platform's implementation. +/// +/// {@macro webview_flutter.WebViewWidget.fromPlatformCreationParams} +/// +/// Below is an example of accessing the platform-specific implementation for +/// iOS and Android: +/// +/// ```dart +/// final WebViewController controller = WebViewController(); +/// +/// final WebViewWidget webViewWidget = WebViewWidget(controller: controller); +/// +/// if (WebViewPlatform.instance is WebKitWebViewPlatform) { +/// final WebKitWebViewWidget webKitWidget = +/// webViewWidget.platform as WebKitWebViewWidget; +/// } else if (WebViewPlatform.instance is AndroidWebViewPlatform) { +/// final AndroidWebViewWidget androidWidget = +/// webViewWidget.platform as AndroidWebViewWidget; +/// } +/// ``` class WebViewWidget extends StatelessWidget { /// Constructs a [WebViewWidget]. + /// + /// See [WebViewWidget.fromPlatformCreationParams] for setting parameters for + /// a specific platform. WebViewWidget({ Key? key, required WebViewController controller, @@ -27,8 +54,40 @@ class WebViewWidget extends StatelessWidget { ), ); - /// Constructs a [WebViewWidget] from creation params for a specific - /// platform. + /// Constructs a [WebViewWidget] from creation params for a specific platform. + /// + /// {@template webview_flutter.WebViewWidget.fromPlatformCreationParams} + /// Below is an example of setting platform-specific creation parameters for + /// iOS and Android: + /// + /// ```dart + /// final WebViewController controller = WebViewController(); + /// + /// PlatformWebViewWidgetCreationParams params = + /// PlatformWebViewWidgetCreationParams( + /// controller: controller.platform, + /// layoutDirection: TextDirection.ltr, + /// gestureRecognizers: const >{}, + /// ); + /// + /// if (WebViewPlatform.instance is WebKitWebViewPlatform) { + /// params = WebKitWebViewWidgetCreationParams + /// .fromPlatformWebViewWidgetCreationParams( + /// params, + /// ); + /// } else if (WebViewPlatform.instance is AndroidWebViewPlatform) { + /// params = AndroidWebViewWidgetCreationParams + /// .fromPlatformWebViewWidgetCreationParams( + /// params, + /// ); + /// } + /// + /// final WebViewWidget webViewWidget = + /// WebViewWidget.fromPlatformCreationParams( + /// params: params, + /// ); + /// ``` + /// {@endtemplate} WebViewWidget.fromPlatformCreationParams({ Key? key, required PlatformWebViewWidgetCreationParams params, diff --git a/packages/webview_flutter/webview_flutter/pubspec.yaml b/packages/webview_flutter/webview_flutter/pubspec.yaml index a494f9e9276c..5cef1a731739 100644 --- a/packages/webview_flutter/webview_flutter/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter/pubspec.yaml @@ -2,7 +2,7 @@ name: webview_flutter description: A Flutter plugin that provides a WebView widget on Android and iOS. repository: https://github.com/flutter/plugins/tree/main/packages/webview_flutter/webview_flutter issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22 -version: 4.0.3 +version: 4.0.4 environment: sdk: ">=2.17.0 <3.0.0" From 1f7b579177ec6138aa48119aa3c831834e871d74 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Sun, 19 Feb 2023 12:50:24 -0500 Subject: [PATCH 125/130] Roll Flutter from 0be7c3f30d64 to 33e4d21f7c13 (5 revisions) (#7196) * 43e74c05e a7c28d085 Roll Fuchsia Linux SDK from hi7JwgHijuYYKAFUR... to Iykltk3-HtXqYplbg... (flutter/engine#39750) (flutter/flutter#121047) * ab39d076c Roll Flutter Engine from a7c28d0851dd to 8d13f3761460 (2 revisions) (flutter/flutter#121050) * 424538990 1b71ea81a Roll ICU from 266a46937f05 to c6b685223182 (4 revisions) (flutter/engine#39753) (flutter/flutter#121054) * 3c6a25d7d 4a58ff869 Roll Fuchsia Mac SDK from nvf4Ago0k-VS2JPxZ... to HtmcMFg6ZlyRkcNsB... (flutter/engine#39754) (flutter/flutter#121056) * 33e4d21f7 00ce81fdf Roll Fuchsia Linux SDK from Iykltk3-HtXqYplbg... to 7rgqQxifQPjH_2zXB... (flutter/engine#39755) (flutter/flutter#121058) --- .ci/flutter_master.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/flutter_master.version b/.ci/flutter_master.version index b2177178bc38..ec9a0909f40f 100644 --- a/.ci/flutter_master.version +++ b/.ci/flutter_master.version @@ -1 +1 @@ -0be7c3f30d647423e3bec24ebbb5f89e03e79b96 +33e4d21f7c13e02a7c92c7272309afbff792a864 From 1acaf55c2d1f87603b9af577665c6f156f8800f5 Mon Sep 17 00:00:00 2001 From: Preston Schwartz Date: Sun, 19 Feb 2023 16:08:22 -0600 Subject: [PATCH 126/130] [plugins] Disables the AndroidGradlePluginVersion issue ID in all android packages (#7045) * Disables the AndroidGradlePluginVersion issue ID in all android packages. * Reverted changelog changes * Reverted all changelog changes. * Fixes changelog conflicts across android packages. --------- Co-authored-by: Stuart Morgan --- packages/camera/camera_android/android/build.gradle | 5 +---- packages/espresso/android/build.gradle | 5 +---- .../flutter_plugin_android_lifecycle/android/build.gradle | 5 +---- .../google_maps_flutter_android/android/build.gradle | 5 +---- .../google_sign_in_android/android/build.gradle | 5 +---- .../image_picker/image_picker_android/android/build.gradle | 4 +--- .../in_app_purchase_android/android/build.gradle | 5 +---- packages/local_auth/local_auth_android/android/build.gradle | 5 +---- .../path_provider/path_provider_android/android/build.gradle | 5 +---- .../quick_actions/quick_actions_android/android/build.gradle | 5 +---- .../shared_preferences_android/android/build.gradle | 4 +--- .../url_launcher/url_launcher_android/android/build.gradle | 4 +--- .../video_player/video_player_android/android/build.gradle | 4 +--- .../webview_flutter_android/android/build.gradle | 4 +--- 14 files changed, 14 insertions(+), 51 deletions(-) diff --git a/packages/camera/camera_android/android/build.gradle b/packages/camera/camera_android/android/build.gradle index 7ed8ac77d371..9c403e02bbd4 100644 --- a/packages/camera/camera_android/android/build.gradle +++ b/packages/camera/camera_android/android/build.gradle @@ -35,10 +35,7 @@ android { testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } lintOptions { - // TODO(stuartmorgan): Enable when gradle is updated. - // disable 'AndroidGradlePluginVersion' - disable 'InvalidPackage' - disable 'GradleDependency' + disable 'AndroidGradlePluginVersion', 'InvalidPackage', 'GradleDependency' baseline file("lint-baseline.xml") } compileOptions { diff --git a/packages/espresso/android/build.gradle b/packages/espresso/android/build.gradle index 47a81b6eea27..6a64f66fc1e7 100644 --- a/packages/espresso/android/build.gradle +++ b/packages/espresso/android/build.gradle @@ -29,10 +29,7 @@ android { testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } lintOptions { - // TODO(stuartmorgan): Enable when gradle is updated. - // disable 'AndroidGradlePluginVersion' - disable 'InvalidPackage' - disable 'GradleDependency' + disable 'AndroidGradlePluginVersion', 'InvalidPackage', 'GradleDependency' baseline file("lint-baseline.xml") } diff --git a/packages/flutter_plugin_android_lifecycle/android/build.gradle b/packages/flutter_plugin_android_lifecycle/android/build.gradle index ae7e518b69da..62c603262989 100644 --- a/packages/flutter_plugin_android_lifecycle/android/build.gradle +++ b/packages/flutter_plugin_android_lifecycle/android/build.gradle @@ -30,10 +30,7 @@ android { consumerProguardFiles 'proguard.txt' } lintOptions { - // TODO(stuartmorgan): Enable when gradle is updated. - // disable 'AndroidGradlePluginVersion' - disable 'InvalidPackage' - disable 'GradleDependency' + disable 'AndroidGradlePluginVersion', 'InvalidPackage', 'GradleDependency' } dependencies { diff --git a/packages/google_maps_flutter/google_maps_flutter_android/android/build.gradle b/packages/google_maps_flutter/google_maps_flutter_android/android/build.gradle index 79486a2716a2..6f8d3060a9cf 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/android/build.gradle +++ b/packages/google_maps_flutter/google_maps_flutter_android/android/build.gradle @@ -29,10 +29,7 @@ android { testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } lintOptions { - // TODO(stuartmorgan): Enable when gradle is updated. - // disable 'AndroidGradlePluginVersion' - disable 'InvalidPackage' - disable 'GradleDependency' + disable 'AndroidGradlePluginVersion', 'InvalidPackage', 'GradleDependency' } dependencies { diff --git a/packages/google_sign_in/google_sign_in_android/android/build.gradle b/packages/google_sign_in/google_sign_in_android/android/build.gradle index 9bc00197e03b..21b7fa178c8f 100644 --- a/packages/google_sign_in/google_sign_in_android/android/build.gradle +++ b/packages/google_sign_in/google_sign_in_android/android/build.gradle @@ -29,10 +29,7 @@ android { testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } lintOptions { - // TODO(stuartmorgan): Enable when gradle is updated. - // disable 'AndroidGradlePluginVersion' - disable 'InvalidPackage' - disable 'GradleDependency' + disable 'AndroidGradlePluginVersion', 'InvalidPackage', 'GradleDependency' } diff --git a/packages/image_picker/image_picker_android/android/build.gradle b/packages/image_picker/image_picker_android/android/build.gradle index 145ee96ca19e..e61f3161d0f5 100644 --- a/packages/image_picker/image_picker_android/android/build.gradle +++ b/packages/image_picker/image_picker_android/android/build.gradle @@ -29,9 +29,7 @@ android { testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } lintOptions { - disable 'AndroidGradlePluginVersion' - disable 'InvalidPackage' - disable 'GradleDependency' + disable 'AndroidGradlePluginVersion', 'InvalidPackage', 'GradleDependency' } dependencies { implementation 'androidx.core:core:1.8.0' diff --git a/packages/in_app_purchase/in_app_purchase_android/android/build.gradle b/packages/in_app_purchase/in_app_purchase_android/android/build.gradle index fe9a958580ba..0d4bde6183cd 100644 --- a/packages/in_app_purchase/in_app_purchase_android/android/build.gradle +++ b/packages/in_app_purchase/in_app_purchase_android/android/build.gradle @@ -29,10 +29,7 @@ android { testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } lintOptions { - // TODO(stuartmorgan): Enable when gradle is updated. - // disable 'AndroidGradlePluginVersion' - disable 'InvalidPackage' - disable 'GradleDependency' + disable 'AndroidGradlePluginVersion', 'InvalidPackage', 'GradleDependency' } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 diff --git a/packages/local_auth/local_auth_android/android/build.gradle b/packages/local_auth/local_auth_android/android/build.gradle index c3ae48a65735..8e116709d6cc 100644 --- a/packages/local_auth/local_auth_android/android/build.gradle +++ b/packages/local_auth/local_auth_android/android/build.gradle @@ -29,10 +29,7 @@ android { testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } lintOptions { - // TODO(stuartmorgan): Enable when gradle is updated. - // disable 'AndroidGradlePluginVersion' - disable 'InvalidPackage' - disable 'GradleDependency' + disable 'AndroidGradlePluginVersion', 'InvalidPackage', 'GradleDependency' baseline file("lint-baseline.xml") } diff --git a/packages/path_provider/path_provider_android/android/build.gradle b/packages/path_provider/path_provider_android/android/build.gradle index c525cd388f5a..926142e5eaf8 100644 --- a/packages/path_provider/path_provider_android/android/build.gradle +++ b/packages/path_provider/path_provider_android/android/build.gradle @@ -29,10 +29,7 @@ android { testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } lintOptions { - // TODO(stuartmorgan): Enable when gradle is updated. - // disable 'AndroidGradlePluginVersion' - disable 'InvalidPackage' - disable 'GradleDependency' + disable 'AndroidGradlePluginVersion', 'InvalidPackage', 'GradleDependency' } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 diff --git a/packages/quick_actions/quick_actions_android/android/build.gradle b/packages/quick_actions/quick_actions_android/android/build.gradle index e096e907ab70..4291fa020ef9 100644 --- a/packages/quick_actions/quick_actions_android/android/build.gradle +++ b/packages/quick_actions/quick_actions_android/android/build.gradle @@ -29,10 +29,7 @@ android { testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } lintOptions { - // TODO(stuartmorgan): Enable when gradle is updated. - // disable 'AndroidGradlePluginVersion' - disable 'InvalidPackage' - disable 'GradleDependency' + disable 'AndroidGradlePluginVersion', 'InvalidPackage', 'GradleDependency' } dependencies { diff --git a/packages/shared_preferences/shared_preferences_android/android/build.gradle b/packages/shared_preferences/shared_preferences_android/android/build.gradle index 480e815d06f1..29f946bf7f77 100644 --- a/packages/shared_preferences/shared_preferences_android/android/build.gradle +++ b/packages/shared_preferences/shared_preferences_android/android/build.gradle @@ -37,9 +37,7 @@ android { testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } lintOptions { - disable 'AndroidGradlePluginVersion' - disable 'InvalidPackage' - disable 'GradleDependency' + disable 'AndroidGradlePluginVersion', 'InvalidPackage', 'GradleDependency' baseline file("lint-baseline.xml") } dependencies { diff --git a/packages/url_launcher/url_launcher_android/android/build.gradle b/packages/url_launcher/url_launcher_android/android/build.gradle index b3d103c32081..63d81249ed8e 100644 --- a/packages/url_launcher/url_launcher_android/android/build.gradle +++ b/packages/url_launcher/url_launcher_android/android/build.gradle @@ -29,9 +29,7 @@ android { testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } lintOptions { - disable 'AndroidGradlePluginVersion' - disable 'InvalidPackage' - disable 'GradleDependency' + disable 'AndroidGradlePluginVersion', 'InvalidPackage', 'GradleDependency' } diff --git a/packages/video_player/video_player_android/android/build.gradle b/packages/video_player/video_player_android/android/build.gradle index daf3b8f4b83e..903ee219d881 100644 --- a/packages/video_player/video_player_android/android/build.gradle +++ b/packages/video_player/video_player_android/android/build.gradle @@ -34,9 +34,7 @@ android { testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } lintOptions { - disable 'AndroidGradlePluginVersion' - disable 'InvalidPackage' - disable 'GradleDependency' + disable 'AndroidGradlePluginVersion', 'InvalidPackage', 'GradleDependency' } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 diff --git a/packages/webview_flutter/webview_flutter_android/android/build.gradle b/packages/webview_flutter/webview_flutter_android/android/build.gradle index f053954e5755..6783e1c977c2 100644 --- a/packages/webview_flutter/webview_flutter_android/android/build.gradle +++ b/packages/webview_flutter/webview_flutter_android/android/build.gradle @@ -30,9 +30,7 @@ android { } lintOptions { - disable 'AndroidGradlePluginVersion' - disable 'InvalidPackage' - disable 'GradleDependency' + disable 'AndroidGradlePluginVersion', 'InvalidPackage', 'GradleDependency' } dependencies { From 132d9c77da4ddf3585518fefcae547d207be30a4 Mon Sep 17 00:00:00 2001 From: stuartmorgan Date: Sun, 19 Feb 2023 17:54:25 -0800 Subject: [PATCH 127/130] [espresso] Update some dependencies (#7195) --- packages/espresso/CHANGELOG.md | 4 ++++ packages/espresso/android/build.gradle | 12 ++++++------ packages/espresso/pubspec.yaml | 2 +- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/packages/espresso/CHANGELOG.md b/packages/espresso/CHANGELOG.md index 715a3bca5786..96ccb32f0325 100644 --- a/packages/espresso/CHANGELOG.md +++ b/packages/espresso/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.2.0+8 + +* Updates espresso and junit dependencies. + ## 0.2.0+7 * Updates espresso gradle and gson dependencies. diff --git a/packages/espresso/android/build.gradle b/packages/espresso/android/build.gradle index 6a64f66fc1e7..bda13fc52780 100644 --- a/packages/espresso/android/build.gradle +++ b/packages/espresso/android/build.gradle @@ -66,17 +66,17 @@ dependencies { api 'androidx.test:rules:1.1.0' // Assertions - api 'androidx.test.ext:junit:1.1.3' + api 'androidx.test.ext:junit:1.1.5' api 'androidx.test.ext:truth:1.5.0' api 'com.google.truth:truth:0.42' // Espresso dependencies - api 'androidx.test.espresso:espresso-core:3.1.0' - api 'androidx.test.espresso:espresso-contrib:3.1.0' - api 'androidx.test.espresso:espresso-intents:3.1.0' + api 'androidx.test.espresso:espresso-core:3.5.1' + api 'androidx.test.espresso:espresso-contrib:3.5.1' + api 'androidx.test.espresso:espresso-intents:3.5.1' api 'androidx.test.espresso:espresso-accessibility:3.5.1' - api 'androidx.test.espresso:espresso-web:3.1.0' - api 'androidx.test.espresso.idling:idling-concurrent:3.1.0' + api 'androidx.test.espresso:espresso-web:3.5.1' + api 'androidx.test.espresso.idling:idling-concurrent:3.5.1' // The following Espresso dependency can be either "implementation" // or "androidTestImplementation", depending on whether you want the diff --git a/packages/espresso/pubspec.yaml b/packages/espresso/pubspec.yaml index 350e3a681db7..21aa5dfb27d9 100644 --- a/packages/espresso/pubspec.yaml +++ b/packages/espresso/pubspec.yaml @@ -3,7 +3,7 @@ description: Java classes for testing Flutter apps using Espresso. Allows driving Flutter widgets from a native Espresso test. repository: https://github.com/flutter/plugins/tree/main/packages/espresso issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+espresso%22 -version: 0.2.0+7 +version: 0.2.0+8 environment: sdk: ">=2.12.0 <3.0.0" From 34a681353c780a3feb0625dc3babb15bb06858a3 Mon Sep 17 00:00:00 2001 From: Abel1027 <64153497+Abel1027@users.noreply.github.com> Date: Tue, 21 Feb 2023 17:14:53 +0100 Subject: [PATCH 128/130] [local_auth] Add Android theme compatibility documentation (#6875) Add documentation to README.md file from local_auth package: - Compatibility of Android theme for Android 8 and below. --- packages/local_auth/local_auth/CHANGELOG.md | 3 +- packages/local_auth/local_auth/README.md | 36 +++++++++++++++++++++ packages/local_auth/local_auth/pubspec.yaml | 2 +- 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/packages/local_auth/local_auth/CHANGELOG.md b/packages/local_auth/local_auth/CHANGELOG.md index d5ad7aa9a28a..0028704b34b1 100644 --- a/packages/local_auth/local_auth/CHANGELOG.md +++ b/packages/local_auth/local_auth/CHANGELOG.md @@ -1,6 +1,7 @@ -## NEXT +## 2.1.4 * Updates minimum Flutter version to 3.0. +* Updates documentation for Android version 8 and below theme compatibility. ## 2.1.3 diff --git a/packages/local_auth/local_auth/README.md b/packages/local_auth/local_auth/README.md index a68692ea940a..8abf583b9dd4 100644 --- a/packages/local_auth/local_auth/README.md +++ b/packages/local_auth/local_auth/README.md @@ -252,6 +252,42 @@ types (such as face scanning) and you want to support SDKs lower than Q, _do not_ call `getAvailableBiometrics`. Simply call `authenticate` with `biometricOnly: true`. This will return an error if there was no hardware available. +#### Android theme + +Your `LaunchTheme`'s parent must be a valid `Theme.AppCompat` theme to prevent +crashes on Android 8 and below. For example, use `Theme.AppCompat.DayNight` to +enable light/dark modes for the biometric dialog. To do that go to +`android/app/src/main/res/values/styles.xml` and look for the style with name +`LaunchTheme`. Then change the parent for that style as follows: + +```xml +... + + + ... + +... +``` + +If you don't have a `styles.xml` file for your Android project you can set up +the Android theme directly in `android/app/src/main/AndroidManifest.xml`: + +```xml +... + + + +... +``` + ## Sticky Auth You can set the `stickyAuth` option on the plugin to true so that plugin does not diff --git a/packages/local_auth/local_auth/pubspec.yaml b/packages/local_auth/local_auth/pubspec.yaml index 769de34b2bb6..c2d3a007d10b 100644 --- a/packages/local_auth/local_auth/pubspec.yaml +++ b/packages/local_auth/local_auth/pubspec.yaml @@ -3,7 +3,7 @@ description: Flutter plugin for Android and iOS devices to allow local authentication via fingerprint, touch ID, face ID, passcode, pin, or pattern. repository: https://github.com/flutter/plugins/tree/main/packages/local_auth/local_auth issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+local_auth%22 -version: 2.1.3 +version: 2.1.4 environment: sdk: ">=2.14.0 <3.0.0" From 557d3284ac9dda32a1106bb75be8a23bdccd2f96 Mon Sep 17 00:00:00 2001 From: stuartmorgan Date: Tue, 21 Feb 2023 16:22:16 -0800 Subject: [PATCH 129/130] Update READMEs for archiving (#7210) Adds notes to README and CONTRIBUTING pointing to flutter/packages. --- CONTRIBUTING.md | 6 +++++- README.md | 8 +++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c2d44d50049b..8441f06a5884 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,10 @@ # Contributing to Flutter Plugins -[![Build Status](https://api.cirrus-ci.com/github/flutter/plugins.svg)](https://cirrus-ci.com/github/flutter/plugins/main) +| **ARCHIVED** | +|--------------| +| This repository is no longer in use; all source has moved to [flutter/packages](https://github.com/flutter/packages) and future development work will be done there. | + +## ARCHIVED CONTENT _See also: [Flutter's code of conduct](https://github.com/flutter/flutter/blob/master/CODE_OF_CONDUCT.md)_ diff --git a/README.md b/README.md index e7dcaf9be610..df38e848a6ae 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,10 @@ # Flutter plugins -[![Build Status](https://api.cirrus-ci.com/github/flutter/plugins.svg)](https://cirrus-ci.com/github/flutter/plugins/main) -[![Release Status](https://github.com/flutter/plugins/actions/workflows/release.yml/badge.svg)](https://github.com/flutter/plugins/actions/workflows/release.yml) -[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/flutter/plugins/badge)](https://deps.dev/project/github/flutter%2Fplugins) +| **ARCHIVED** | +|--------------| +| This repository is no longer in use; all source has moved to [flutter/packages](https://github.com/flutter/packages) and future development work will be done there. | + +## ARCHIVED CONTENT This repo is a companion repo to the main [flutter repo](https://github.com/flutter/flutter). It contains the source code for From c59b47aff5f5cf697957d84621c7b176d1f8ecec Mon Sep 17 00:00:00 2001 From: "jakub.walusiak" Date: Mon, 6 Mar 2023 09:53:35 +0100 Subject: [PATCH 130/130] [in_app_purchases_android_platform] Changed method names in BillingClientManager --- .../billing_client_manager.dart | 57 +++++++++---------- .../src/in_app_purchase_android_platform.dart | 13 +++-- ...pp_purchase_android_platform_addition.dart | 10 ++-- .../billing_client_manager_test.dart | 22 +++---- 4 files changed, 52 insertions(+), 50 deletions(-) diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_manager.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_manager.dart index 31598621da05..e7ee7c90f054 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_manager.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_manager.dart @@ -24,7 +24,8 @@ abstract class HasBillingResponse { /// re-initialized. /// /// [BillingClient] instance is not exposed directly. It can be accessed via -/// [run] and [runRaw] methods that handle the connection management. +/// [withClient] and [withClientNonRetryable] methods that handle the +/// connection management. /// /// Consider calling [dispose] after the [BillingClient] is no longer needed. class BillingClientManager { @@ -44,7 +45,8 @@ class BillingClientManager { /// [BillingClient] instance managed by this [BillingClientManager]. /// - /// In order to access the [BillingClient], consider using [run] and [runRaw] + /// In order to access the [BillingClient], consider using [withClient] + /// and [withClientNonRetryable] /// methods. @visibleForTesting late final BillingClient client = BillingClient(_onPurchasesUpdated); @@ -58,46 +60,48 @@ class BillingClientManager { // Initialized immediately in the constructor, so it's always safe to access. late Future _readyFuture; - /// Executes the given [block] with access to the underlying [BillingClient]. + /// Executes the given [action] with access to the underlying [BillingClient]. /// /// If necessary, waits for the underlying [BillingClient] to connect. - /// If given [block] returns [BillingResponse.serviceDisconnected], it will + /// If given [action] returns [BillingResponse.serviceDisconnected], it will /// be transparently retried after the connection is restored. Because - /// of this, [block] may be called multiple times. + /// of this, [action] may be called multiple times. /// /// A response with [BillingResponse.serviceDisconnected] may be returned /// in case of [dispose] being called during the operation. /// - /// See [runRaw] for operations that do not return a subclass + /// See [withClientNonRetryable] for operations that do not return a subclass /// of [HasBillingResponse]. - Future run( - Future Function(BillingClient client) block, + Future withClient( + Future Function(BillingClient client) action, ) async { - assert(_debugAssertNotDisposed()); + _debugAssertNotDisposed(); await _readyFuture; - final R result = await block(client); + final R result = await action(client); if (result.responseCode == BillingResponse.serviceDisconnected && !_isDisposed) { await _connect(); - return run(block); + return withClient(action); } else { return result; } } - /// Executes the given [block] with access to the underlying [BillingClient]. + /// Executes the given [action] with access to the underlying [BillingClient]. /// /// If necessary, waits for the underlying [BillingClient] to connect. /// Designed only for operations that do not return a subclass /// of [HasBillingResponse] (e.g. [BillingClient.isReady], /// [BillingClient.isFeatureSupported]). /// - /// See [runRaw] for operations that return a subclass + /// See [withClient] for operations that return a subclass /// of [HasBillingResponse]. - Future runRaw(Future Function(BillingClient client) block) async { - assert(_debugAssertNotDisposed()); + Future withClientNonRetryable( + Future Function(BillingClient client) action, + ) async { + _debugAssertNotDisposed(); await _readyFuture; - return block(client); + return action(client); } /// Ends connection to the [BillingClient]. @@ -108,9 +112,9 @@ class BillingClientManager { /// After calling [dispose] : /// - Further connection attempts will not be made; /// - [purchasesUpdatedStream] will be closed; - /// - Calls to [run] and [runRaw] will throw. + /// - Calls to [withClient] and [withClientNonRetryable] will throw. void dispose() { - assert(_debugAssertNotDisposed()); + _debugAssertNotDisposed(); _isDisposed = true; client.endConnection(); _purchasesUpdatedController.close(); @@ -141,16 +145,11 @@ class BillingClientManager { _purchasesUpdatedController.add(event); } - bool _debugAssertNotDisposed() { - assert(() { - if (_isDisposed) { - throw FlutterError( - 'A BillingClientManager was used after being disposed.\n' - 'Once you have called dispose() on a BillingClientManager, it can no longer be used.', - ); - } - return true; - }()); - return true; + void _debugAssertNotDisposed() { + assert( + !_isDisposed, + 'A BillingClientManager was used after being disposed. Once you have ' + 'called dispose() on a BillingClientManager, it can no longer be used.', + ); } } diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform.dart index 0dea21a579d6..57d6850d9e93 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform.dart @@ -63,7 +63,7 @@ class InAppPurchaseAndroidPlatform extends InAppPurchasePlatform { @override Future isAvailable() async { return billingClientManager - .runRaw((BillingClient client) => client.isReady()); + .withClientNonRetryable((BillingClient client) => client.isReady()); } @override @@ -73,7 +73,7 @@ class InAppPurchaseAndroidPlatform extends InAppPurchasePlatform { PlatformException? exception; Future querySkuDetails(SkuType type) { - return billingClientManager.run( + return billingClientManager.withClient( (BillingClient client) => client.querySkuDetails( skuType: type, skusList: identifiers.toList(), @@ -96,6 +96,7 @@ class InAppPurchaseAndroidPlatform extends InAppPurchasePlatform { ), skuDetailsList: const [], ); + // Error response for both queries should be the same, so we can reuse it. responses = [response, response]; } final List productDetailsList = @@ -131,7 +132,7 @@ class InAppPurchaseAndroidPlatform extends InAppPurchasePlatform { } final BillingResultWrapper billingResultWrapper = - await billingClientManager.run( + await billingClientManager.withClient( (BillingClient client) => client.launchBillingFlow( sku: purchaseParam.productDetails.id, accountId: purchaseParam.applicationUserName, @@ -173,7 +174,7 @@ class InAppPurchaseAndroidPlatform extends InAppPurchasePlatform { 'completePurchase unsuccessful. The `purchase.verificationData` is not valid'); } - return billingClientManager.run( + return billingClientManager.withClient( (BillingClient client) => client.acknowledgePurchase( purchase.verificationData.serverVerificationData), ); @@ -187,9 +188,9 @@ class InAppPurchaseAndroidPlatform extends InAppPurchasePlatform { responses = await Future.wait(>[ billingClientManager - .run((BillingClient client) => client.queryPurchases(SkuType.inapp)), + .withClient((BillingClient client) => client.queryPurchases(SkuType.inapp)), billingClientManager - .run((BillingClient client) => client.queryPurchases(SkuType.subs)), + .withClient((BillingClient client) => client.queryPurchases(SkuType.subs)), ]); final Set errorCodeSet = responses diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform_addition.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform_addition.dart index d5bfc73868a2..255071f0c0bf 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform_addition.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform_addition.dart @@ -54,7 +54,7 @@ class InAppPurchaseAndroidPlatformAddition throw ArgumentError( 'consumePurchase unsuccessful. The `purchase.verificationData` is not valid'); } - return _billingClientManager.run( + return _billingClientManager.withClient( (BillingClient client) => client.consumeAsync(purchase.verificationData.serverVerificationData), ); @@ -80,10 +80,10 @@ class InAppPurchaseAndroidPlatformAddition PlatformException? exception; try { responses = await Future.wait(>[ - _billingClientManager.run( + _billingClientManager.withClient( (BillingClient client) => client.queryPurchases(SkuType.inapp), ), - _billingClientManager.run( + _billingClientManager.withClient( (BillingClient client) => client.queryPurchases(SkuType.subs), ), ]); @@ -148,7 +148,7 @@ class InAppPurchaseAndroidPlatformAddition /// Call this to check if a [BillingClientFeature] is supported by the device. Future isFeatureSupported(BillingClientFeature feature) async { return _billingClientManager - .runRaw((BillingClient client) => client.isFeatureSupported(feature)); + .withClientNonRetryable((BillingClient client) => client.isFeatureSupported(feature)); } /// Initiates a flow to confirm the change of price for an item subscribed by the user. @@ -160,7 +160,7 @@ class InAppPurchaseAndroidPlatformAddition /// [InAppPurchaseAndroidPlatform.queryProductDetails] call. Future launchPriceChangeConfirmationFlow( {required String sku}) { - return _billingClientManager.run( + return _billingClientManager.withClient( (BillingClient client) => client.launchPriceChangeConfirmationFlow(sku: sku), ); diff --git a/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/billing_client_manager_test.dart b/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/billing_client_manager_test.dart index 8351395cb46b..bd64fabe8002 100644 --- a/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/billing_client_manager_test.dart +++ b/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/billing_client_manager_test.dart @@ -51,18 +51,20 @@ void main() { }); test('waits for connection before executing the operations', () { - bool runCalled = false; - bool runRawCalled = false; - manager.run((BillingClient _) async { - runCalled = true; + bool called1 = false; + bool called2 = false; + manager.withClient((BillingClient _) async { + called1 = true; return const BillingResultWrapper(responseCode: BillingResponse.ok); }); - manager.runRaw((BillingClient _) async => runRawCalled = true); - expect(runCalled, equals(false)); - expect(runRawCalled, equals(false)); + manager.withClientNonRetryable( + (BillingClient _) async => called2 = true, + ); + expect(called1, equals(false)); + expect(called2, equals(false)); connectedCompleter.complete(); - expect(runCalled, equals(true)); - expect(runRawCalled, equals(true)); + expect(called1, equals(true)); + expect(called2, equals(true)); }); test('re-connects when client sends onBillingServiceDisconnected', () { @@ -79,7 +81,7 @@ void main() { () async { connectedCompleter.complete(); int timesCalled = 0; - final BillingResultWrapper result = await manager.run( + final BillingResultWrapper result = await manager.withClient( (BillingClient _) async { timesCalled++; return BillingResultWrapper(