From 9ea2befdd2d89d5aeaf99568f66a627411309175 Mon Sep 17 00:00:00 2001 From: misos1 <30872003+misos1@users.noreply.github.com> Date: Wed, 15 Jan 2025 17:34:58 +0100 Subject: [PATCH 1/9] [video_player_avfoundation] fix playback speed resetting (#7657) Calling play is the same as setting the rate to 1.0 (or to `defaultRate` depending on ios version, *documentation in this first link is clearly not updated because it does not mention `defaultRate`*): https://developer.apple.com/documentation/avfoundation/avplayer/1386726-play?language=objc https://developer.apple.com/documentation/avfoundation/avplayer/3929373-defaultrate?language=objc *The second link contains a note about not starting playback by setting the rate to 1.0. I assume this is because of the introduction of `defaultRate` (which can be different than 1.0) and not because `play` may do something more than just setting `rate` as that wording is explicit about setting rate to 1.0, it says nothing about any other value.* This is also why https://github.com/flutter/plugins/pull/4331 did not work well. It was setting `rate` to 1.0 (through `play`) then immediately to the value set by `setPlaybackSpeed`. One of them or both caused change to `playbackLikelyToKeepUp` which triggered `observeValueForKeyPath` with `playbackLikelyToKeepUpContext` which in turn called again `updatePlayingState` where was `rate` again changed first to 1.0 then to another value and so on. In this PR the rate is changed only once and then to the same value, seems when `rate` is assigned but not really changed it does not trigger anything. In issues below `seekTo` can trigger `playbackLikelyToKeepUp` change which will call `updatePlayingState` and reset `rate` to 1.0 through `play`. When the speed is set in dart before initialization then the dart side will set it right after calling `play` but even some time after initialization there is still a flood of events calling `updatePlayingState` and it is likely that some of them will call it after `setPlaybackSpeed`. Actually even when `setPlaybackSpeed` was not called on dart side it (dart side) will always change speed after play to 1.0 so it ignores this new `defaultRate` feature. - fixes https://github.com/flutter/flutter/issues/71264 - fixes https://github.com/flutter/flutter/issues/73643 --- .../video_player_avfoundation/CHANGELOG.md | 4 ++ .../darwin/RunnerTests/VideoPlayerTests.m | 37 +++++++++++ .../FVPVideoPlayer.m | 61 ++++++++++++------- .../video_player_avfoundation/pubspec.yaml | 2 +- 4 files changed, 81 insertions(+), 23 deletions(-) diff --git a/packages/video_player/video_player_avfoundation/CHANGELOG.md b/packages/video_player/video_player_avfoundation/CHANGELOG.md index 76b14ba3f96..9c6f47ee7dd 100644 --- a/packages/video_player/video_player_avfoundation/CHANGELOG.md +++ b/packages/video_player/video_player_avfoundation/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.6.7 + +* Fixes playback speed resetting. + ## 2.6.6 * Fixes changing global audio session category to be collision free across plugins. diff --git a/packages/video_player/video_player_avfoundation/darwin/RunnerTests/VideoPlayerTests.m b/packages/video_player/video_player_avfoundation/darwin/RunnerTests/VideoPlayerTests.m index 559c9f089d6..4150250d168 100644 --- a/packages/video_player/video_player_avfoundation/darwin/RunnerTests/VideoPlayerTests.m +++ b/packages/video_player/video_player_avfoundation/darwin/RunnerTests/VideoPlayerTests.m @@ -672,6 +672,8 @@ - (void)testSeekToleranceWhenSeekingToEnd { // Change playback speed. [videoPlayerPlugin setPlaybackSpeed:2 forPlayer:textureId.integerValue error:&error]; XCTAssertNil(error); + [videoPlayerPlugin playPlayer:textureId.integerValue error:&error]; + XCTAssertNil(error); XCTAssertEqual(avPlayer.rate, 2); XCTAssertEqual(avPlayer.timeControlStatus, AVPlayerTimeControlStatusWaitingToPlayAtSpecifiedRate); @@ -839,6 +841,41 @@ - (void)testFailedToLoadVideoEventShouldBeAlwaysSent { [self waitForExpectationsWithTimeout:10.0 handler:nil]; } +- (void)testUpdatePlayingStateShouldNotResetRate { + NSObject *registrar = + [GetPluginRegistry() registrarForPlugin:@"testUpdatePlayingStateShouldNotResetRate"]; + + FVPVideoPlayerPlugin *videoPlayerPlugin = [[FVPVideoPlayerPlugin alloc] + initWithAVFactory:[[StubFVPAVFactory alloc] initWithPlayer:nil output:nil] + displayLinkFactory:nil + registrar:registrar]; + + FlutterError *error; + [videoPlayerPlugin initialize:&error]; + XCTAssertNil(error); + FVPCreationOptions *create = [FVPCreationOptions + makeWithAsset:nil + uri:@"https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4" + packageName:nil + formatHint:nil + httpHeaders:@{}]; + NSNumber *textureId = [videoPlayerPlugin createWithOptions:create error:&error]; + FVPVideoPlayer *player = videoPlayerPlugin.playersByTextureId[textureId]; + + XCTestExpectation *initializedExpectation = [self expectationWithDescription:@"initialized"]; + [player onListenWithArguments:nil + eventSink:^(NSDictionary *event) { + if ([event[@"event"] isEqualToString:@"initialized"]) { + [initializedExpectation fulfill]; + } + }]; + [self waitForExpectationsWithTimeout:10 handler:nil]; + + [videoPlayerPlugin setPlaybackSpeed:2 forPlayer:textureId.integerValue error:&error]; + [videoPlayerPlugin playPlayer:textureId.integerValue error:&error]; + XCTAssertEqual(player.player.rate, 2); +} + #if TARGET_OS_IOS - (void)testVideoPlayerShouldNotOverwritePlayAndRecordNorDefaultToSpeaker { NSObject *registrar = [GetPluginRegistry() diff --git a/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/FVPVideoPlayer.m b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/FVPVideoPlayer.m index 5892274a37d..087cf401db5 100644 --- a/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/FVPVideoPlayer.m +++ b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/FVPVideoPlayer.m @@ -29,6 +29,8 @@ @interface FVPVideoPlayer () @property(nonatomic) CGAffineTransform preferredTransform; /// Indicates whether the video player is currently playing. @property(nonatomic, readonly) BOOL isPlaying; +/// The target playback speed requested by the plugin client. +@property(nonatomic, readonly) NSNumber *targetPlaybackSpeed; /// Indicates whether the video player has been initialized. @property(nonatomic, readonly) BOOL isInitialized; /// The updater that drives callbacks to the engine to indicate that a new frame is ready. @@ -323,7 +325,15 @@ - (void)updatePlayingState { return; } if (_isPlaying) { - [_player play]; + // Calling play is the same as setting the rate to 1.0 (or to defaultRate depending on iOS + // version) so last set playback speed must be set here if any instead. + // https://github.com/flutter/flutter/issues/71264 + // https://github.com/flutter/flutter/issues/73643 + if (_targetPlaybackSpeed) { + [self updateRate]; + } else { + [_player play]; + } } else { [_player pause]; } @@ -332,6 +342,32 @@ - (void)updatePlayingState { _displayLink.running = _isPlaying || self.waitingForFrame; } +/// Synchronizes the player's playback rate with targetPlaybackSpeed, constrained by the playback +/// rate capabilities of the player's current item. +- (void)updateRate { + // See https://developer.apple.com/library/archive/qa/qa1772/_index.html for an explanation of + // these checks. + // If status is not AVPlayerItemStatusReadyToPlay then both canPlayFastForward + // and canPlaySlowForward are always false and it is unknown whether video can + // be played at these speeds, updatePlayingState will be called again when + // status changes to AVPlayerItemStatusReadyToPlay. + float speed = _targetPlaybackSpeed.floatValue; + BOOL readyToPlay = _player.currentItem.status == AVPlayerItemStatusReadyToPlay; + if (speed > 2.0 && !_player.currentItem.canPlayFastForward) { + if (!readyToPlay) { + return; + } + speed = 2.0; + } + if (speed < 1.0 && !_player.currentItem.canPlaySlowForward) { + if (!readyToPlay) { + return; + } + speed = 1.0; + } + _player.rate = speed; +} + - (void)sendFailedToLoadVideoEvent { if (_eventSink == nil) { return; @@ -473,27 +509,8 @@ - (void)setVolume:(double)volume { } - (void)setPlaybackSpeed:(double)speed { - // See https://developer.apple.com/library/archive/qa/qa1772/_index.html for an explanation of - // these checks. - if (speed > 2.0 && !_player.currentItem.canPlayFastForward) { - if (_eventSink != nil) { - _eventSink([FlutterError errorWithCode:@"VideoError" - message:@"Video cannot be fast-forwarded beyond 2.0x" - details:nil]); - } - return; - } - - if (speed < 1.0 && !_player.currentItem.canPlaySlowForward) { - if (_eventSink != nil) { - _eventSink([FlutterError errorWithCode:@"VideoError" - message:@"Video cannot be slow-forwarded" - details:nil]); - } - return; - } - - _player.rate = speed; + _targetPlaybackSpeed = @(speed); + [self updatePlayingState]; } - (CVPixelBufferRef)copyPixelBuffer { diff --git a/packages/video_player/video_player_avfoundation/pubspec.yaml b/packages/video_player/video_player_avfoundation/pubspec.yaml index c20357d7804..c7f9dadb777 100644 --- a/packages/video_player/video_player_avfoundation/pubspec.yaml +++ b/packages/video_player/video_player_avfoundation/pubspec.yaml @@ -2,7 +2,7 @@ name: video_player_avfoundation description: iOS and macOS implementation of the video_player plugin. repository: https://github.com/flutter/packages/tree/main/packages/video_player/video_player_avfoundation issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+video_player%22 -version: 2.6.6 +version: 2.6.7 environment: sdk: ^3.4.0 From 6d98122ce211cf2622b0a50096b769c3e54d6bb3 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Wed, 15 Jan 2025 12:16:20 -0500 Subject: [PATCH 2/9] Roll Flutter (stable) from 17025dd88227 to 68415ad1d920 (4 revisions) (#8434) https://github.com/flutter/flutter/compare/17025dd88227...68415ad1d920 If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/flutter-stable-packages Please CC stuartmorgan@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Flutter (stable): https://github.com/flutter/flutter/issues/new/choose To file a bug in Packages: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- .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 557fe15614b..5c6f92135c6 100644 --- a/.ci/flutter_stable.version +++ b/.ci/flutter_stable.version @@ -1 +1 @@ -17025dd88227cd9532c33fa78f5250d548d87e9a +68415ad1d920f6fe5ec284f5c2febf7c4dd5b0b3 From e949ba7563a169c3a10f4541df13029edbd2293d Mon Sep 17 00:00:00 2001 From: LouiseHsu Date: Wed, 15 Jan 2025 15:58:40 -0800 Subject: [PATCH 3/9] [in_app_purchase_storekit] expose `jsonRepresentation` for Transactions (#8430) Exposes [jsonRepresentation](https://developer.apple.com/documentation/storekit/transaction/jsonrepresentation) for Transactions. Helpful for developers to who want to access the properties of Transaction directly if they arent already exposed. Fixes https://github.com/flutter/flutter/issues/158882 --- .../in_app_purchase_storekit/CHANGELOG.md | 4 +++ .../InAppPurchasePlugin+StoreKit2.swift | 6 ++-- .../StoreKit2/StoreKit2Translators.swift | 3 +- .../Classes/StoreKit2/sk2_pigeon.g.swift | 10 +++++-- .../InAppPurchaseStoreKit2PluginTests.swift | 28 +++++++++++++++++++ .../lib/src/sk2_pigeon.g.dart | 5 ++++ .../pigeons/sk2_pigeon.dart | 2 ++ .../in_app_purchase_storekit/pubspec.yaml | 2 +- 8 files changed, 52 insertions(+), 8 deletions(-) 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 289f3c36b03..8952b5a0be3 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.20+4 + +* Exposes `jsonRepresentation` field for transactions. + ## 0.3.20+3 * Fixes `finishTransaction` not completing. diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/InAppPurchasePlugin+StoreKit2.swift b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/InAppPurchasePlugin+StoreKit2.swift index af4a1934589..672860beec6 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/InAppPurchasePlugin+StoreKit2.swift +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/InAppPurchasePlugin+StoreKit2.swift @@ -179,10 +179,10 @@ extension InAppPurchasePlugin: InAppPurchase2API { } } - // MARK: - Convenience Functions + // MARK: - Internal Convenience Functions /// Helper function that fetches and unwraps all verified transactions - private func rawTransactions() async -> [Transaction] { + func rawTransactions() async -> [Transaction] { var transactions: [Transaction] = [] for await verificationResult in Transaction.all { switch verificationResult { @@ -196,7 +196,7 @@ extension InAppPurchasePlugin: InAppPurchase2API { } /// Helper function to fetch specific transaction - private func fetchTransaction(by id: UInt64) async throws -> Transaction? { + func fetchTransaction(by id: UInt64) async throws -> Transaction? { for await result in Transaction.all { switch result { case .verified(let transaction): diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/StoreKit2Translators.swift b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/StoreKit2Translators.swift index dc662b9cbdd..80658bceafa 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/StoreKit2Translators.swift +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/StoreKit2Translators.swift @@ -200,7 +200,8 @@ extension Transaction { purchasedQuantity: Int64(purchasedQuantity), appAccountToken: appAccountToken?.uuidString, restoring: receipt != nil, - receiptData: receipt + receiptData: receipt, + jsonRepresentation: String(decoding: jsonRepresentation, as: UTF8.self) ) } } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/sk2_pigeon.g.swift b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/sk2_pigeon.g.swift index c6923001143..5defe53680e 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/sk2_pigeon.g.swift +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/sk2_pigeon.g.swift @@ -318,6 +318,7 @@ struct SK2TransactionMessage { var restoring: Bool var receiptData: String? = nil var error: SK2ErrorMessage? = nil + var jsonRepresentation: String? = nil // swift-format-ignore: AlwaysUseLowerCamelCase static func fromList(_ pigeonVar_list: [Any?]) -> SK2TransactionMessage? { @@ -331,6 +332,7 @@ struct SK2TransactionMessage { let restoring = pigeonVar_list[7] as! Bool let receiptData: String? = nilOrValue(pigeonVar_list[8]) let error: SK2ErrorMessage? = nilOrValue(pigeonVar_list[9]) + let jsonRepresentation: String? = nilOrValue(pigeonVar_list[10]) return SK2TransactionMessage( id: id, @@ -342,7 +344,8 @@ struct SK2TransactionMessage { appAccountToken: appAccountToken, restoring: restoring, receiptData: receiptData, - error: error + error: error, + jsonRepresentation: jsonRepresentation ) } func toList() -> [Any?] { @@ -357,6 +360,7 @@ struct SK2TransactionMessage { restoring, receiptData, error, + jsonRepresentation, ] } } @@ -524,8 +528,8 @@ class InAppPurchase2APISetup { static var codec: FlutterStandardMessageCodec { sk2_pigeonPigeonCodec.shared } /// Sets up an instance of `InAppPurchase2API` to handle messages through the `binaryMessenger`. static func setUp( - binaryMessenger: FlutterBinaryMessenger, api: InAppPurchase2API?, - messageChannelSuffix: String = "" + binaryMessenger: FlutterBinaryMessenger, + api: InAppPurchase2API?, messageChannelSuffix: String = "" ) { let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : "" let canMakePaymentsChannel = FlutterBasicMessageChannel( diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift index b16548adc32..1a861ef7305 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift @@ -129,6 +129,34 @@ final class InAppPurchase2PluginTests: XCTestCase { XCTAssert(fetchedProductMsg?.count == 0) } + func testGetTransactionJsonRepresentation() async throws { + let expectation = self.expectation(description: "Purchase request should succeed") + + plugin.purchase(id: "consumable", options: nil) { result in + switch result { + case .success(_): + expectation.fulfill() + case .failure(let error): + XCTFail("Purchase should NOT fail. Failed with \(error)") + } + } + + await fulfillment(of: [expectation], timeout: 5) + + let transaction = try await plugin.fetchTransaction( + by: UInt64(session.allTransactions()[0].originalTransactionIdentifier)) + + guard let transaction = transaction else { + XCTFail("Transaction does not exist.") + return + } + + let jsonRepresentationString = String(decoding: transaction.jsonRepresentation, as: UTF8.self) + + XCTAssert(jsonRepresentationString.localizedStandardContains("Type\":\"Consumable")) + XCTAssert(jsonRepresentationString.localizedStandardContains("storefront\":\"USA")) + } + //TODO(louisehsu): Add testing for lower versions. @available(iOS 17.0, macOS 14.0, *) func testGetProductsWithStoreKitError() async throws { diff --git a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/sk2_pigeon.g.dart b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/sk2_pigeon.g.dart index 1339471a088..c4476badce3 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/sk2_pigeon.g.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/sk2_pigeon.g.dart @@ -306,6 +306,7 @@ class SK2TransactionMessage { this.restoring = false, this.receiptData, this.error, + this.jsonRepresentation, }); int id; @@ -328,6 +329,8 @@ class SK2TransactionMessage { SK2ErrorMessage? error; + String? jsonRepresentation; + Object encode() { return [ id, @@ -340,6 +343,7 @@ class SK2TransactionMessage { restoring, receiptData, error, + jsonRepresentation, ]; } @@ -356,6 +360,7 @@ class SK2TransactionMessage { restoring: result[7]! as bool, receiptData: result[8] as String?, error: result[9] as SK2ErrorMessage?, + jsonRepresentation: result[10] as String?, ); } } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/pigeons/sk2_pigeon.dart b/packages/in_app_purchase/in_app_purchase_storekit/pigeons/sk2_pigeon.dart index 77239af4723..c6493ce17ee 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/pigeons/sk2_pigeon.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/pigeons/sk2_pigeon.dart @@ -146,6 +146,7 @@ class SK2TransactionMessage { this.appAccountToken, this.error, this.receiptData, + this.jsonRepresentation, this.restoring = false}); final int id; final int originalId; @@ -157,6 +158,7 @@ class SK2TransactionMessage { final bool restoring; final String? receiptData; final SK2ErrorMessage? error; + final String? jsonRepresentation; } class SK2ErrorMessage { 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 c8e929e0b6b..07adfb8ab1b 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/packages/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.20+3 +version: 0.3.20+4 environment: sdk: ^3.4.0 From 973e8b59e24ba80d3c36a2bcfa914fcfd5e19943 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Thu, 16 Jan 2025 11:28:48 -0500 Subject: [PATCH 4/9] Roll Flutter from 40c2b86fdf65 to 5517cc9b3b3b (28 revisions) (#8441) https://github.com/flutter/flutter/compare/40c2b86fdf65...5517cc9b3b3b 2025-01-15 37028599+EArminjon@users.noreply.github.com feat: Change default value of keyboardDismissBehavior (flutter/flutter#158580) 2025-01-15 137456488+flutter-pub-roller-bot@users.noreply.github.com Roll pub packages (flutter/flutter#161680) 2025-01-15 jacob.simionato@gmail.com Revert "Autocomplete Options Width" (flutter/flutter#161666) 2025-01-15 stuartmorgan@google.com Update two_dimensional_scrollables triage routing (flutter/flutter#161667) 2025-01-15 flar@google.com [DisplayList] Migrate from SkRSXform to Impeller RSTransform (flutter/flutter#161652) 2025-01-15 engine-flutter-autoroll@skia.org Roll Packages from d1fd6232ec33 to f73cb00e127b (2 revisions) (flutter/flutter#161672) 2025-01-15 bruno.leroux@gmail.com Fix DropdownMenu isCollapsed decoration does not Reduce height (flutter/flutter#161427) 2025-01-15 jason-simmons@users.noreply.github.com Manual roll of Skia to e7b8d078851f (flutter/flutter#161609) 2025-01-15 tessertaha@gmail.com Fix `TabBar` glitchy elastic `Tab` animation (flutter/flutter#161514) 2025-01-15 137456488+flutter-pub-roller-bot@users.noreply.github.com Roll pub packages (flutter/flutter#161643) 2025-01-15 matanlurey@users.noreply.github.com Exclude the top-level `engine` directory from `generate_gradle_lockfiles`. (flutter/flutter#161635) 2025-01-15 137456488+flutter-pub-roller-bot@users.noreply.github.com Roll pub packages (flutter/flutter#161632) 2025-01-15 matanlurey@users.noreply.github.com Refactor `android_engine_test`, make it easier to debug/deflake locally. (flutter/flutter#161534) 2025-01-15 jonahwilliams@google.com [Impeller] null check device buffer in image encoding. (flutter/flutter#161194) 2025-01-15 nabilamevia2003@gmail.com Feature/twitter keyboard (flutter/flutter#161025) 2025-01-15 jessiewong401@gmail.com Fixed XiaoMi statusBar Bug (flutter/flutter#161271) 2025-01-15 goderbauer@google.com Clean up engine's analysis_options.yaml (flutter/flutter#161554) 2025-01-14 34871572+gmackall@users.noreply.github.com Remove `gradle_deprecated_settings` test app, and remove reference from lockfile exclusion yaml (flutter/flutter#161622) 2025-01-14 devoncarew@google.com [deps] remove no-longer-used repo deps (flutter/flutter#161605) 2025-01-14 flar@google.com [DisplayList] remove obsolete use of Skia goemetry objects in DL utils (flutter/flutter#161553) 2025-01-14 dkwingsmt@users.noreply.github.com [Engine] Support asymmetrical rounded superellipses (flutter/flutter#161409) 2025-01-14 737941+loic-sharma@users.noreply.github.com [SwiftPM] Make 'flutter build ios-framework' generate an empty Package.swift (flutter/flutter#161464) 2025-01-14 1961493+harryterkelsen@users.noreply.github.com [canvaskit] Fix GIF decode failure (flutter/flutter#161536) 2025-01-14 jonahwilliams@google.com [Impeller] fixes for AHB swapchains. (flutter/flutter#161562) 2025-01-14 goderbauer@google.com Last Engine<>Framework lint sync (flutter/flutter#161560) 2025-01-14 goderbauer@google.com Check that localization files of stocks app are up-to-date (flutter/flutter#161608) 2025-01-14 43054281+camsim99@users.noreply.github.com [Android] Actually remove dev dependencies from release builds (flutter/flutter#161343) 2025-01-14 bkonyi@google.com Update package revisions to latest (flutter/flutter#161525) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/flutter-packages Please CC stuartmorgan@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Packages: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- .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 092179ece8d..5c5d3b2938d 100644 --- a/.ci/flutter_master.version +++ b/.ci/flutter_master.version @@ -1 +1 @@ -40c2b86fdf650cb95ad69915cd724191ee36de32 +5517cc9b3b3bcf12431b47f495e342a30b738835 From cfc56a2103c469bc6fd3ddf12056147bb24dd1cd Mon Sep 17 00:00:00 2001 From: Jenn Magder Date: Fri, 17 Jan 2025 11:51:26 -0800 Subject: [PATCH 5/9] [local_auth_darwin] Handle when FaceID hardware is available but permissions have been denied for the app (#8348) LocalAuth [`-canEvaluatePolicy::`](https://developer.apple.com/documentation/localauthentication/lacontext/canevaluatepolicy(_:error:)?language=objc) returns error `LAErrorBiometryNotAvailable` when either Face ID isn't available at the hardware level, OR the Face ID is available/setup but the permissions for the app were denied. If it returns `LAErrorBiometryNotAvailable`, also check `biometryType` since this _should_ be none if there really is no biometry, but gets populated with [`LABiometryTypeTouchID`](https://developer.apple.com/documentation/localauthentication/labiometrytype/touchid?language=objc) when the FaceID hardware is available, but permission is denied. I tried this on a iPhone 16 Pro which has Face ID but not Touch ID support. Fixes https://github.com/flutter/flutter/issues/160083 --- .../local_auth/local_auth_darwin/CHANGELOG.md | 3 ++- .../Tests/FLALocalAuthPluginTests.swift | 24 +++++++++++++++++-- .../local_auth_darwin/FLALocalAuthPlugin.m | 5 ++++ .../local_auth/local_auth_darwin/pubspec.yaml | 2 +- 4 files changed, 30 insertions(+), 4 deletions(-) diff --git a/packages/local_auth/local_auth_darwin/CHANGELOG.md b/packages/local_auth/local_auth_darwin/CHANGELOG.md index c5fcf90d34a..78c17dbc4c9 100644 --- a/packages/local_auth/local_auth_darwin/CHANGELOG.md +++ b/packages/local_auth/local_auth_darwin/CHANGELOG.md @@ -1,5 +1,6 @@ -## NEXT +## 1.4.3 +* Handles when biometry hardware is available but permissions have been denied for the app. * Updates minimum supported SDK version to Flutter 3.22/Dart 3.4. ## 1.4.2 diff --git a/packages/local_auth/local_auth_darwin/darwin/Tests/FLALocalAuthPluginTests.swift b/packages/local_auth/local_auth_darwin/darwin/Tests/FLALocalAuthPluginTests.swift index 73da7123b38..c8e1324a277 100644 --- a/packages/local_auth/local_auth_darwin/darwin/Tests/FLALocalAuthPluginTests.swift +++ b/packages/local_auth/local_auth_darwin/darwin/Tests/FLALocalAuthPluginTests.swift @@ -447,7 +447,7 @@ class FLALocalAuthPluginTests: XCTestCase { XCTAssertNil(error) } - func testDeviceSupportsBiometrics_withNoBiometricHardware() { + func testDeviceSupportsBiometrics_withBiometryNotAvailable() { let stubAuthContext = StubAuthContext() let alertFactory = StubAlertFactory() let viewProvider = StubViewProvider() @@ -456,7 +456,8 @@ class FLALocalAuthPluginTests: XCTestCase { alertFactory: alertFactory, viewProvider: viewProvider) stubAuthContext.expectBiometrics = true - stubAuthContext.canEvaluateError = NSError(domain: "error", code: 0) + stubAuthContext.canEvaluateError = NSError( + domain: "error", code: LAError.biometryNotAvailable.rawValue) var error: FlutterError? let result = plugin.deviceCanSupportBiometricsWithError(&error) @@ -464,6 +465,25 @@ class FLALocalAuthPluginTests: XCTestCase { XCTAssertNil(error) } + func testDeviceSupportsBiometrics_withBiometryNotAvailableLoadedBiometryType() { + let stubAuthContext = StubAuthContext() + let alertFactory = StubAlertFactory() + let viewProvider = StubViewProvider() + let plugin = FLALocalAuthPlugin( + contextFactory: StubAuthContextFactory(contexts: [stubAuthContext]), + alertFactory: alertFactory, viewProvider: viewProvider) + + stubAuthContext.expectBiometrics = true + stubAuthContext.biometryType = LABiometryType.touchID + stubAuthContext.canEvaluateError = NSError( + domain: "error", code: LAError.biometryNotAvailable.rawValue) + + var error: FlutterError? + let result = plugin.deviceCanSupportBiometricsWithError(&error) + XCTAssertTrue(result!.boolValue) + XCTAssertNil(error) + } + func testGetEnrolledBiometricsWithFaceID() { let stubAuthContext = StubAuthContext() let alertFactory = StubAlertFactory() diff --git a/packages/local_auth/local_auth_darwin/darwin/local_auth_darwin/Sources/local_auth_darwin/FLALocalAuthPlugin.m b/packages/local_auth/local_auth_darwin/darwin/local_auth_darwin/Sources/local_auth_darwin/FLALocalAuthPlugin.m index bc50a59a41f..33bd25df70e 100644 --- a/packages/local_auth/local_auth_darwin/darwin/local_auth_darwin/Sources/local_auth_darwin/FLALocalAuthPlugin.m +++ b/packages/local_auth/local_auth_darwin/darwin/local_auth_darwin/Sources/local_auth_darwin/FLALocalAuthPlugin.m @@ -317,6 +317,11 @@ - (nullable NSNumber *)deviceCanSupportBiometricsWithError: if (authError.code == LAErrorBiometryNotEnrolled) { return @YES; } + // Biometry hardware is available, but possibly permissions were denied. + if (authError.code == LAErrorBiometryNotAvailable && + context.biometryType != LABiometryTypeNone) { + return @YES; + } } return @NO; diff --git a/packages/local_auth/local_auth_darwin/pubspec.yaml b/packages/local_auth/local_auth_darwin/pubspec.yaml index 8aff58fe369..df3b43489f2 100644 --- a/packages/local_auth/local_auth_darwin/pubspec.yaml +++ b/packages/local_auth/local_auth_darwin/pubspec.yaml @@ -2,7 +2,7 @@ name: local_auth_darwin description: iOS implementation of the local_auth plugin. repository: https://github.com/flutter/packages/tree/main/packages/local_auth/local_auth_darwin issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+local_auth%22 -version: 1.4.2 +version: 1.4.3 environment: sdk: ^3.4.0 From 205960d8442295d3f368e442787d002638189ea6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 17 Jan 2025 19:52:49 +0000 Subject: [PATCH 6/9] [dependabot]: Bump the gradle-plugin group across 3 directories with 1 update (#8328) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the gradle-plugin group with 1 update in the /packages/interactive_media_ads/android directory: [org.jetbrains.kotlin:kotlin-gradle-plugin](https://github.com/JetBrains/kotlin). Bumps the gradle-plugin group with 1 update in the /packages/shared_preferences/shared_preferences_android/android directory: [org.jetbrains.kotlin:kotlin-gradle-plugin](https://github.com/JetBrains/kotlin). Bumps the gradle-plugin group with 1 update in the /packages/webview_flutter/webview_flutter_android/android directory: [org.jetbrains.kotlin:kotlin-gradle-plugin](https://github.com/JetBrains/kotlin). Updates `org.jetbrains.kotlin:kotlin-gradle-plugin` from 1.9.10 to 2.1.0
Release notes

Sourced from org.jetbrains.kotlin:kotlin-gradle-plugin's releases.

Kotlin 2.1.0

Changelog

Analysis API

New Features

  • KT-68603 KotlinDirectInheritorsProvider: add an option to ignore non-kotlin results

Performance Improvements

  • KT-70757 Performance problem in KaFirVisibilityChecker for KaFirPsiJavaClassSymbol

Fixes

  • KT-70437 Class reference is not resolvable
  • KT-57733 Analysis API: Use optimized ModuleWithDependenciesScopes in combined symbol providers
  • KT-72389 K2: False positive "Redundant 'protected' modifier" for protected property inside protected constructor from private or internal class
  • KT-69190 K2: False-positive "redundant private modifier"
  • KT-64984 Analysis API: Support Wasm target
  • KT-70375 K2: NPE at org.jetbrains.kotlin.analysis.api.fir.symbols.KaFirNamedClassSymbolBase.createPointer
  • KT-71259 K2 evaluator: Invalid smart cast info collecting for Code Fragments
  • KT-69360 Lack of implicit receiver for the last statement under lambda in scripts
  • KT-70890 Analysis API: Experiment with weak references to LL FIR/analysis sessions in session caches
  • KT-70657 Analysis API: Inner types from classes with generics are incorrectly represented by the compiled jars
  • KT-71055 Suspend calls inside 'analyze()' break the block guarantees
  • KT-70815 Analysis API: Implement stop-the-world session invalidation
  • KT-69819 K2 IDE: LHS type in callable references is unresolved when it has type arguments and is qualified
  • KT-68761 Analysis API: Experiment with limited-size cache in KaFirSessionProvider
  • KT-70384 Analysis API Standalone: The same class in the same two renamed jars is unresolved
  • KT-71067 Exceptions from references cancel Find Usages
  • KT-69535 Redesign 'containingSymbol'
  • KT-71025 K2 IDE: Scopes in "importingScopeContext" have reversed ordering and "indexInTower" values
  • KT-67483 K2 IDE: Serializable plugin causes infinite resolve recursion when there is a star import from a class with annotation call
  • KT-69416 K2 IDE / Completion: “No classifier found” on simple value creating
  • KT-70257 CCE: class kotlin.UInt cannot be cast to class java.lang.Number
  • KT-70376 K2 IDE / Kotlin Debugger: IAE “Only componentN functions should be cached this way, but got: toString” on evaluating toString() method for value class
  • KT-70264 AA: service registration via XML fails with AbstractMethodError in Lint CLI
  • KT-69950 Analysis API: Introduce isSubtypeOf(ClassId)
  • KT-68625 K2: “lazyResolveToPhase(STATUS) cannot be called from a transformer with a phase STATUS.”
  • KT-67665 K2: contract violation for value class with a constructor parameter with an implicit type
  • KT-67009 Analysis API: Add abbreviated type tests for type aliases from source modules
  • KT-69977 KaFirFunctionalType#getAbbreviation is always null
  • KT-68341 Analysis API: Expanded function types from libraries don't have an abbreviated type
  • KT-68857 Analysis API: Refactor annotations
  • KT-70386 Do not filter out overloads from different libraries in dangling files
  • KT-65552 K2: CANNOT_CHECK_FOR_ERASED in KtTypeCodeFragment
  • KT-65803 K2: Analysis API: KtFirTypeProvider#getSubstitutedSuperTypes throws an exception in the case of "Wrong number of type arguments"
  • KT-68896 Support VirtualFile binary dependency inputs to Analysis API modules
  • KT-69395 K2 IDE: incorrect overload selection from binary dependencies in a shared native source set

... (truncated)

Changelog

Sourced from org.jetbrains.kotlin:kotlin-gradle-plugin's changelog.

2.1.0

Analysis API

New Features

  • KT-68603 KotlinDirectInheritorsProvider: add an option to ignore non-kotlin results

Performance Improvements

  • KT-70757 Performance problem in KaFirVisibilityChecker for KaFirPsiJavaClassSymbol

Fixes

  • KT-70437 Class reference is not resolvable
  • KT-57733 Analysis API: Use optimized ModuleWithDependenciesScopes in combined symbol providers
  • KT-72389 K2: False positive "Redundant 'protected' modifier" for protected property inside protected constructor from private or internal class
  • KT-69190 K2: False-positive "redundant private modifier"
  • KT-64984 Analysis API: Support Wasm target
  • KT-70375 K2: NPE at org.jetbrains.kotlin.analysis.api.fir.symbols.KaFirNamedClassSymbolBase.createPointer
  • KT-71259 K2 evaluator: Invalid smart cast info collecting for Code Fragments
  • KT-69360 Lack of implicit receiver for the last statement under lambda in scripts
  • KT-70890 Analysis API: Experiment with weak references to LL FIR/analysis sessions in session caches
  • KT-70657 Analysis API: Inner types from classes with generics are incorrectly represented by the compiled jars
  • KT-71055 Suspend calls inside 'analyze()' break the block guarantees
  • KT-70815 Analysis API: Implement stop-the-world session invalidation
  • KT-69819 K2 IDE: LHS type in callable references is unresolved when it has type arguments and is qualified
  • KT-68761 Analysis API: Experiment with limited-size cache in KaFirSessionProvider
  • KT-70384 Analysis API Standalone: The same class in the same two renamed jars is unresolved
  • KT-71067 Exceptions from references cancel Find Usages
  • KT-69535 Redesign 'containingSymbol'
  • KT-71025 K2 IDE: Scopes in "importingScopeContext" have reversed ordering and "indexInTower" values
  • KT-67483 K2 IDE: Serializable plugin causes infinite resolve recursion when there is a star import from a class with annotation call
  • KT-69416 K2 IDE / Completion: “No classifier found” on simple value creating
  • KT-70257 CCE: class kotlin.UInt cannot be cast to class java.lang.Number
  • KT-70376 K2 IDE / Kotlin Debugger: IAE “Only componentN functions should be cached this way, but got: toString” on evaluating toString() method for value class
  • KT-70264 AA: service registration via XML fails with AbstractMethodError in Lint CLI
  • KT-69950 Analysis API: Introduce isSubtypeOf(ClassId)
  • KT-68625 K2: “lazyResolveToPhase(STATUS) cannot be called from a transformer with a phase STATUS.”
  • KT-67665 K2: contract violation for value class with a constructor parameter with an implicit type
  • KT-67009 Analysis API: Add abbreviated type tests for type aliases from source modules
  • KT-69977 KaFirFunctionalType#getAbbreviation is always null
  • KT-68341 Analysis API: Expanded function types from libraries don't have an abbreviated type
  • KT-68857 Analysis API: Refactor annotations
  • KT-70386 Do not filter out overloads from different libraries in dangling files
  • KT-65552 K2: CANNOT_CHECK_FOR_ERASED in KtTypeCodeFragment
  • KT-65803 K2: Analysis API: KtFirTypeProvider#getSubstitutedSuperTypes throws an exception in the case of "Wrong number of type arguments"
  • KT-68896 Support VirtualFile binary dependency inputs to Analysis API modules
  • KT-69395 K2 IDE: incorrect overload selection from binary dependencies in a shared native source set
  • KT-68573 ISE: "Unexpected constant value (kotlin/annotation/AnnotationTarget, CLASS)" at Kt1DescUtilsKt.toKtConstantValue()

... (truncated)

Commits
  • 5dd9cea Add ChangeLog for 2.1.0
  • be31f19 [Gradle] Fix documentation publishing to Kotlinlang
  • f959bf2 Add ChangeLog for 2.1.0-RC2
  • b21df7b [Gradle] Update info about versioning
  • a7dabb6 [Gradle] Fix templates extraction
  • 858b914 [Gradle] Add KDoc for KotlinTargetsDsl
  • 1026b47 [Gradle] Add KDoc for KotlinTargetWithTests
  • 54452c0 [Gradle] Add KDoc for KotlinTestRun
  • d10e47a [Gradle] Add KDoc for KotlinExecution
  • 1208eec fix: clarifications and language polishing
  • Additional commits viewable in compare view

Updates `org.jetbrains.kotlin:kotlin-gradle-plugin` from 1.7.10 to 2.1.0
Release notes

Sourced from org.jetbrains.kotlin:kotlin-gradle-plugin's releases.

Kotlin 2.1.0

Changelog

Analysis API

New Features

  • KT-68603 KotlinDirectInheritorsProvider: add an option to ignore non-kotlin results

Performance Improvements

  • KT-70757 Performance problem in KaFirVisibilityChecker for KaFirPsiJavaClassSymbol

Fixes

  • KT-70437 Class reference is not resolvable
  • KT-57733 Analysis API: Use optimized ModuleWithDependenciesScopes in combined symbol providers
  • KT-72389 K2: False positive "Redundant 'protected' modifier" for protected property inside protected constructor from private or internal class
  • KT-69190 K2: False-positive "redundant private modifier"
  • KT-64984 Analysis API: Support Wasm target
  • KT-70375 K2: NPE at org.jetbrains.kotlin.analysis.api.fir.symbols.KaFirNamedClassSymbolBase.createPointer
  • KT-71259 K2 evaluator: Invalid smart cast info collecting for Code Fragments
  • KT-69360 Lack of implicit receiver for the last statement under lambda in scripts
  • KT-70890 Analysis API: Experiment with weak references to LL FIR/analysis sessions in session caches
  • KT-70657 Analysis API: Inner types from classes with generics are incorrectly represented by the compiled jars
  • KT-71055 Suspend calls inside 'analyze()' break the block guarantees
  • KT-70815 Analysis API: Implement stop-the-world session invalidation
  • KT-69819 K2 IDE: LHS type in callable references is unresolved when it has type arguments and is qualified
  • KT-68761 Analysis API: Experiment with limited-size cache in KaFirSessionProvider
  • KT-70384 Analysis API Standalone: The same class in the same two renamed jars is unresolved
  • KT-71067 Exceptions from references cancel Find Usages
  • KT-69535 Redesign 'containingSymbol'
  • KT-71025 K2 IDE: Scopes in "importingScopeContext" have reversed ordering and "indexInTower" values
  • KT-67483 K2 IDE: Serializable plugin causes infinite resolve recursion when there is a star import from a class with annotation call
  • KT-69416 K2 IDE / Completion: “No classifier found” on simple value creating
  • KT-70257 CCE: class kotlin.UInt cannot be cast to class java.lang.Number
  • KT-70376 K2 IDE / Kotlin Debugger: IAE “Only componentN functions should be cached this way, but got: toString” on evaluating toString() method for value class
  • KT-70264 AA: service registration via XML fails with AbstractMethodError in Lint CLI
  • KT-69950 Analysis API: Introduce isSubtypeOf(ClassId)
  • KT-68625 K2: “lazyResolveToPhase(STATUS) cannot be called from a transformer with a phase STATUS.”
  • KT-67665 K2: contract violation for value class with a constructor parameter with an implicit type
  • KT-67009 Analysis API: Add abbreviated type tests for type aliases from source modules
  • KT-69977 KaFirFunctionalType#getAbbreviation is always null
  • KT-68341 Analysis API: Expanded function types from libraries don't have an abbreviated type
  • KT-68857 Analysis API: Refactor annotations
  • KT-70386 Do not filter out overloads from different libraries in dangling files
  • KT-65552 K2: CANNOT_CHECK_FOR_ERASED in KtTypeCodeFragment
  • KT-65803 K2: Analysis API: KtFirTypeProvider#getSubstitutedSuperTypes throws an exception in the case of "Wrong number of type arguments"
  • KT-68896 Support VirtualFile binary dependency inputs to Analysis API modules
  • KT-69395 K2 IDE: incorrect overload selection from binary dependencies in a shared native source set

... (truncated)

Changelog

Sourced from org.jetbrains.kotlin:kotlin-gradle-plugin's changelog.

2.1.0

Analysis API

New Features

  • KT-68603 KotlinDirectInheritorsProvider: add an option to ignore non-kotlin results

Performance Improvements

  • KT-70757 Performance problem in KaFirVisibilityChecker for KaFirPsiJavaClassSymbol

Fixes

  • KT-70437 Class reference is not resolvable
  • KT-57733 Analysis API: Use optimized ModuleWithDependenciesScopes in combined symbol providers
  • KT-72389 K2: False positive "Redundant 'protected' modifier" for protected property inside protected constructor from private or internal class
  • KT-69190 K2: False-positive "redundant private modifier"
  • KT-64984 Analysis API: Support Wasm target
  • KT-70375 K2: NPE at org.jetbrains.kotlin.analysis.api.fir.symbols.KaFirNamedClassSymbolBase.createPointer
  • KT-71259 K2 evaluator: Invalid smart cast info collecting for Code Fragments
  • KT-69360 Lack of implicit receiver for the last statement under lambda in scripts
  • KT-70890 Analysis API: Experiment with weak references to LL FIR/analysis sessions in session caches
  • KT-70657 Analysis API: Inner types from classes with generics are incorrectly represented by the compiled jars
  • KT-71055 Suspend calls inside 'analyze()' break the block guarantees
  • KT-70815 Analysis API: Implement stop-the-world session invalidation
  • KT-69819 K2 IDE: LHS type in callable references is unresolved when it has type arguments and is qualified
  • KT-68761 Analysis API: Experiment with limited-size cache in KaFirSessionProvider
  • KT-70384 Analysis API Standalone: The same class in the same two renamed jars is unresolved
  • KT-71067 Exceptions from references cancel Find Usages
  • KT-69535 Redesign 'containingSymbol'
  • KT-71025 K2 IDE: Scopes in "importingScopeContext" have reversed ordering and "indexInTower" values
  • KT-67483 K2 IDE: Serializable plugin causes infinite resolve recursion when there is a star import from a class with annotation call
  • KT-69416 K2 IDE / Completion: “No classifier found” on simple value creating
  • KT-70257 CCE: class kotlin.UInt cannot be cast to class java.lang.Number
  • KT-70376 K2 IDE / Kotlin Debugger: IAE “Only componentN functions should be cached this way, but got: toString” on evaluating toString() method for value class
  • KT-70264 AA: service registration via XML fails with AbstractMethodError in Lint CLI
  • KT-69950 Analysis API: Introduce isSubtypeOf(ClassId)
  • KT-68625 K2: “lazyResolveToPhase(STATUS) cannot be called from a transformer with a phase STATUS.”
  • KT-67665 K2: contract violation for value class with a constructor parameter with an implicit type
  • KT-67009 Analysis API: Add abbreviated type tests for type aliases from source modules
  • KT-69977 KaFirFunctionalType#getAbbreviation is always null
  • KT-68341 Analysis API: Expanded function types from libraries don't have an abbreviated type
  • KT-68857 Analysis API: Refactor annotations
  • KT-70386 Do not filter out overloads from different libraries in dangling files
  • KT-65552 K2: CANNOT_CHECK_FOR_ERASED in KtTypeCodeFragment
  • KT-65803 K2: Analysis API: KtFirTypeProvider#getSubstitutedSuperTypes throws an exception in the case of "Wrong number of type arguments"
  • KT-68896 Support VirtualFile binary dependency inputs to Analysis API modules
  • KT-69395 K2 IDE: incorrect overload selection from binary dependencies in a shared native source set
  • KT-68573 ISE: "Unexpected constant value (kotlin/annotation/AnnotationTarget, CLASS)" at Kt1DescUtilsKt.toKtConstantValue()

... (truncated)

Commits
  • 5dd9cea Add ChangeLog for 2.1.0
  • be31f19 [Gradle] Fix documentation publishing to Kotlinlang
  • f959bf2 Add ChangeLog for 2.1.0-RC2
  • b21df7b [Gradle] Update info about versioning
  • a7dabb6 [Gradle] Fix templates extraction
  • 858b914 [Gradle] Add KDoc for KotlinTargetsDsl
  • 1026b47 [Gradle] Add KDoc for KotlinTargetWithTests
  • 54452c0 [Gradle] Add KDoc for KotlinTestRun
  • d10e47a [Gradle] Add KDoc for KotlinExecution
  • 1208eec fix: clarifications and language polishing
  • Additional commits viewable in compare view

Updates `org.jetbrains.kotlin:kotlin-gradle-plugin` from 1.9.10 to 2.1.0
Release notes

Sourced from org.jetbrains.kotlin:kotlin-gradle-plugin's releases.

Kotlin 2.1.0

Changelog

Analysis API

New Features

  • KT-68603 KotlinDirectInheritorsProvider: add an option to ignore non-kotlin results

Performance Improvements

  • KT-70757 Performance problem in KaFirVisibilityChecker for KaFirPsiJavaClassSymbol

Fixes

  • KT-70437 Class reference is not resolvable
  • KT-57733 Analysis API: Use optimized ModuleWithDependenciesScopes in combined symbol providers
  • KT-72389 K2: False positive "Redundant 'protected' modifier" for protected property inside protected constructor from private or internal class
  • KT-69190 K2: False-positive "redundant private modifier"
  • KT-64984 Analysis API: Support Wasm target
  • KT-70375 K2: NPE at org.jetbrains.kotlin.analysis.api.fir.symbols.KaFirNamedClassSymbolBase.createPointer
  • KT-71259 K2 evaluator: Invalid smart cast info collecting for Code Fragments
  • KT-69360 Lack of implicit receiver for the last statement under lambda in scripts
  • KT-70890 Analysis API: Experiment with weak references to LL FIR/analysis sessions in session caches
  • KT-70657 Analysis API: Inner types from classes with generics are incorrectly represented by the compiled jars
  • KT-71055 Suspend calls inside 'analyze()' break the block guarantees
  • KT-70815 Analysis API: Implement stop-the-world session invalidation
  • KT-69819 K2 IDE: LHS type in callable references is unresolved when it has type arguments and is qualified
  • KT-68761 Analysis API: Experiment with limited-size cache in KaFirSessionProvider
  • KT-70384 Analysis API Standalone: The same class in the same two renamed jars is unresolved
  • KT-71067 Exceptions from references cancel Find Usages
  • KT-69535 Redesign 'containingSymbol'
  • KT-71025 K2 IDE: Scopes in "importingScopeContext" have reversed ordering and "indexInTower" values
  • KT-67483 K2 IDE: Serializable plugin causes infinite resolve recursion when there is a star import from a class with annotation call
  • KT-69416 K2 IDE / Completion: “No classifier found” on simple value creating
  • KT-70257 CCE: class kotlin.UInt cannot be cast to class java.lang.Number
  • KT-70376 K2 IDE / Kotlin Debugger: IAE “Only componentN functions should be cached this way, but got: toString” on evaluating toString() method for value class
  • KT-70264 AA: service registration via XML fails with AbstractMethodError in Lint CLI
  • KT-69950 Analysis API: Introduce isSubtypeOf(ClassId)
  • KT-68625 K2: “lazyResolveToPhase(STATUS) cannot be called from a transformer with a phase STATUS.”
  • KT-67665 K2: contract violation for value class with a constructor parameter with an implicit type
  • KT-67009 Analysis API: Add abbreviated type tests for type aliases from source modules
  • KT-69977 KaFirFunctionalType#getAbbreviation is always null
  • KT-68341 Analysis API: Expanded function types from libraries don't have an abbreviated type
  • KT-68857 Analysis API: Refactor annotations
  • KT-70386 Do not filter out overloads from different libraries in dangling files
  • KT-65552 K2: CANNOT_CHECK_FOR_ERASED in KtTypeCodeFragment
  • KT-65803 K2: Analysis API: KtFirTypeProvider#getSubstitutedSuperTypes throws an exception in the case of "Wrong number of type arguments"
  • KT-68896 Support VirtualFile binary dependency inputs to Analysis API modules
  • KT-69395 K2 IDE: incorrect overload selection from binary dependencies in a shared native source set

... (truncated)

Changelog

Sourced from org.jetbrains.kotlin:kotlin-gradle-plugin's changelog.

2.1.0

Analysis API

New Features

  • KT-68603 KotlinDirectInheritorsProvider: add an option to ignore non-kotlin results

Performance Improvements

  • KT-70757 Performance problem in KaFirVisibilityChecker for KaFirPsiJavaClassSymbol

Fixes

  • KT-70437 Class reference is not resolvable
  • KT-57733 Analysis API: Use optimized ModuleWithDependenciesScopes in combined symbol providers
  • KT-72389 K2: False positive "Redundant 'protected' modifier" for protected property inside protected constructor from private or internal class
  • KT-69190 K2: False-positive "redundant private modifier"
  • KT-64984 Analysis API: Support Wasm target
  • KT-70375 K2: NPE at org.jetbrains.kotlin.analysis.api.fir.symbols.KaFirNamedClassSymbolBase.createPointer
  • KT-71259 K2 evaluator: Invalid smart cast info collecting for Code Fragments
  • KT-69360 Lack of implicit receiver for the last statement under lambda in scripts
  • KT-70890 Analysis API: Experiment with weak references to LL FIR/analysis sessions in session caches
  • KT-70657 Analysis API: Inner types from classes with generics are incorrectly represented by the compiled jars
  • KT-71055 Suspend calls inside 'analyze()' break the block guarantees
  • KT-70815 Analysis API: Implement stop-the-world session invalidation
  • KT-69819 K2 IDE: LHS type in callable references is unresolved when it has type arguments and is qualified
  • KT-68761 Analysis API: Experiment with limited-size cache in KaFirSessionProvider
  • KT-70384 Analysis API Standalone: The same class in the same two renamed jars is unresolved
  • KT-71067 Exceptions from references cancel Find Usages
  • KT-69535 Redesign 'containingSymbol'
  • KT-71025 K2 IDE: Scopes in "importingScopeContext" have reversed ordering and "indexInTower" values
  • KT-67483 K2 IDE: Serializable plugin causes infinite resolve recursion when there is a star import from a class with annotation call
  • KT-69416 K2 IDE / Completion: “No classifier found” on simple value creating
  • KT-70257 CCE: class kotlin.UInt cannot be cast to class java.lang.Number
  • KT-70376 K2 IDE / Kotlin Debugger: IAE “Only componentN functions should be cached this way, but got: toString” on evaluating toString() method for value class
  • KT-70264 AA: service registration via XML fails with AbstractMethodError in Lint CLI
  • KT-69950 Analysis API: Introduce isSubtypeOf(ClassId)
  • KT-68625 K2: “lazyResolveToPhase(STATUS) cannot be called from a transformer with a phase STATUS.”
  • KT-67665 K2: contract violation for value class with a constructor parameter with an implicit type
  • KT-67009 Analysis API: Add abbreviated type tests for type aliases from source modules
  • KT-69977 KaFirFunctionalType#getAbbreviation is always null
  • KT-68341 Analysis API: Expanded function types from libraries don't have an abbreviated type
  • KT-68857 Analysis API: Refactor annotations
  • KT-70386 Do not filter out overloads from different libraries in dangling files
  • KT-65552 K2: CANNOT_CHECK_FOR_ERASED in KtTypeCodeFragment
  • KT-65803 K2: Analysis API: KtFirTypeProvider#getSubstitutedSuperTypes throws an exception in the case of "Wrong number of type arguments"
  • KT-68896 Support VirtualFile binary dependency inputs to Analysis API modules
  • KT-69395 K2 IDE: incorrect overload selection from binary dependencies in a shared native source set
  • KT-68573 ISE: "Unexpected constant value (kotlin/annotation/AnnotationTarget, CLASS)" at Kt1DescUtilsKt.toKtConstantValue()

... (truncated)

Commits
  • 5dd9cea Add ChangeLog for 2.1.0
  • be31f19 [Gradle] Fix documentation publishing to Kotlinlang
  • f959bf2 Add ChangeLog for 2.1.0-RC2
  • b21df7b [Gradle] Update info about versioning
  • a7dabb6 [Gradle] Fix templates extraction
  • 858b914 [Gradle] Add KDoc for KotlinTargetsDsl
  • 1026b47 [Gradle] Add KDoc for KotlinTargetWithTests
  • 54452c0 [Gradle] Add KDoc for KotlinTestRun
  • d10e47a [Gradle] Add KDoc for KotlinExecution
  • 1208eec fix: clarifications and language polishing
  • Additional commits viewable in compare view

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
--- packages/interactive_media_ads/CHANGELOG.md | 4 ++++ packages/interactive_media_ads/android/build.gradle | 2 +- .../packages/interactive_media_ads/AdsRequestProxyApi.kt | 2 +- .../interactive_media_ads/AdsRequestProxyAPIDelegate.swift | 2 +- packages/interactive_media_ads/pubspec.yaml | 2 +- .../shared_preferences_android/CHANGELOG.md | 4 ++++ .../shared_preferences_android/android/build.gradle | 2 +- .../shared_preferences_android/pubspec.yaml | 2 +- packages/webview_flutter/webview_flutter_android/CHANGELOG.md | 4 ++++ .../webview_flutter_android/android/build.gradle | 2 +- packages/webview_flutter/webview_flutter_android/pubspec.yaml | 2 +- 11 files changed, 20 insertions(+), 8 deletions(-) diff --git a/packages/interactive_media_ads/CHANGELOG.md b/packages/interactive_media_ads/CHANGELOG.md index 77ff5ea6b22..9986455137b 100644 --- a/packages/interactive_media_ads/CHANGELOG.md +++ b/packages/interactive_media_ads/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.2.3+5 + +* Bumps gradle-plugin to 2.1.0. + ## 0.2.3+4 * Adds remaining methods for internal wrapper of the iOS native `IMAAdDisplayContainer`. diff --git a/packages/interactive_media_ads/android/build.gradle b/packages/interactive_media_ads/android/build.gradle index 2a3dc43d1aa..789f58207ea 100644 --- a/packages/interactive_media_ads/android/build.gradle +++ b/packages/interactive_media_ads/android/build.gradle @@ -2,7 +2,7 @@ group 'dev.flutter.packages.interactive_media_ads' version '1.0-SNAPSHOT' buildscript { - ext.kotlin_version = '1.9.10' + ext.kotlin_version = '2.1.0' repositories { google() mavenCentral() diff --git a/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/AdsRequestProxyApi.kt b/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/AdsRequestProxyApi.kt index 3dec0274efb..eb886b70af3 100644 --- a/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/AdsRequestProxyApi.kt +++ b/packages/interactive_media_ads/android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/AdsRequestProxyApi.kt @@ -21,7 +21,7 @@ class AdsRequestProxyApi(override val pigeonRegistrar: ProxyApiRegistrar) : * * This must match the version in pubspec.yaml. */ - const val pluginVersion = "0.2.3+4" + const val pluginVersion = "0.2.3+5" } override fun setAdTagUrl(pigeon_instance: AdsRequest, adTagUrl: String) { diff --git a/packages/interactive_media_ads/ios/interactive_media_ads/Sources/interactive_media_ads/AdsRequestProxyAPIDelegate.swift b/packages/interactive_media_ads/ios/interactive_media_ads/Sources/interactive_media_ads/AdsRequestProxyAPIDelegate.swift index fbd8ffff3be..1707e13ffab 100644 --- a/packages/interactive_media_ads/ios/interactive_media_ads/Sources/interactive_media_ads/AdsRequestProxyAPIDelegate.swift +++ b/packages/interactive_media_ads/ios/interactive_media_ads/Sources/interactive_media_ads/AdsRequestProxyAPIDelegate.swift @@ -13,7 +13,7 @@ class AdsRequestProxyAPIDelegate: PigeonApiDelegateIMAAdsRequest { /// The current version of the `interactive_media_ads` plugin. /// /// This must match the version in pubspec.yaml. - static let pluginVersion = "0.2.3+4" + static let pluginVersion = "0.2.3+5" func pigeonDefaultConstructor( pigeonApi: PigeonApiIMAAdsRequest, adTagUrl: String, adDisplayContainer: IMAAdDisplayContainer, diff --git a/packages/interactive_media_ads/pubspec.yaml b/packages/interactive_media_ads/pubspec.yaml index 6ac1fc8a1f3..bcf59fdb963 100644 --- a/packages/interactive_media_ads/pubspec.yaml +++ b/packages/interactive_media_ads/pubspec.yaml @@ -2,7 +2,7 @@ name: interactive_media_ads description: A Flutter plugin for using the Interactive Media Ads SDKs on Android and iOS. repository: https://github.com/flutter/packages/tree/main/packages/interactive_media_ads issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+interactive_media_ads%22 -version: 0.2.3+4 # This must match the version in +version: 0.2.3+5 # This must match the version in # `android/src/main/kotlin/dev/flutter/packages/interactive_media_ads/AdsRequestProxyApi.kt` and # `ios/interactive_media_ads/Sources/interactive_media_ads/AdsRequestProxyAPIDelegate.swift` diff --git a/packages/shared_preferences/shared_preferences_android/CHANGELOG.md b/packages/shared_preferences/shared_preferences_android/CHANGELOG.md index b47688db603..197babaebb6 100644 --- a/packages/shared_preferences/shared_preferences_android/CHANGELOG.md +++ b/packages/shared_preferences/shared_preferences_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.4.2 + +* Bumps gradle-plugin to 2.1.0. + ## 2.4.1 * Bumps kotlin version to 1.9.10 androidx.datastore:datastore from 1.0.0 to 1.1.1. diff --git a/packages/shared_preferences/shared_preferences_android/android/build.gradle b/packages/shared_preferences/shared_preferences_android/android/build.gradle index b25d8d6934e..16bbdace333 100644 --- a/packages/shared_preferences/shared_preferences_android/android/build.gradle +++ b/packages/shared_preferences/shared_preferences_android/android/build.gradle @@ -2,7 +2,7 @@ group 'io.flutter.plugins.sharedpreferences' version '1.0-SNAPSHOT' buildscript { - ext.kotlin_version = '1.9.10' + ext.kotlin_version = '2.1.0' repositories { google() mavenCentral() diff --git a/packages/shared_preferences/shared_preferences_android/pubspec.yaml b/packages/shared_preferences/shared_preferences_android/pubspec.yaml index f0156474ca2..e2f143a4a87 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/packages/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.4.1 +version: 2.4.2 environment: sdk: ^3.5.0 diff --git a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md index 539c54c8a9f..b883c27cde7 100644 --- a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## 4.3.1 + +* Bumps gradle-plugin to 2.1.0. + ## 4.3.0 * Adds support for disabling content URL access within WebView and disabling the Geolocation API. diff --git a/packages/webview_flutter/webview_flutter_android/android/build.gradle b/packages/webview_flutter/webview_flutter_android/android/build.gradle index 12c459eaf8b..c7b9a14357f 100644 --- a/packages/webview_flutter/webview_flutter_android/android/build.gradle +++ b/packages/webview_flutter/webview_flutter_android/android/build.gradle @@ -2,7 +2,7 @@ group 'io.flutter.plugins.webviewflutter' version '1.0-SNAPSHOT' buildscript { - ext.kotlin_version = '1.9.10' + ext.kotlin_version = '2.1.0' repositories { google() mavenCentral() diff --git a/packages/webview_flutter/webview_flutter_android/pubspec.yaml b/packages/webview_flutter/webview_flutter_android/pubspec.yaml index bd248b40f5d..4796e93732c 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/packages/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: 4.3.0 +version: 4.3.1 environment: sdk: ^3.5.0 From d6bc0f0eed2eb6e4f27fff3218483818ce0682fd Mon Sep 17 00:00:00 2001 From: stuartmorgan Date: Sat, 18 Jan 2025 07:24:40 -0800 Subject: [PATCH 7/9] [pigeon] Update analyzer and formatter (#8456) Adds compatibility with the latest major version of `analyzer` and `dart_style` (the latter being required to actually allow `analyzer` 7). The breaking change of `dart_style` does affect our usage, so this updates rather than expands the allowed version. The range for `analyzer` is updated to drop versions older thas 6 since `dart_style` doesn't allow versions older than 6 anyway. We have to pick a language version to use for formatting; this (somewhat arbitrarily) uses 3.6, to opt in to the new formatter. Fixes https://github.com/flutter/flutter/issues/161820 Fixes https://github.com/flutter/flutter/issues/161583 --- packages/pigeon/CHANGELOG.md | 4 ++++ packages/pigeon/lib/dart_generator.dart | 7 +++++-- packages/pigeon/lib/generator_tools.dart | 2 +- packages/pigeon/pubspec.yaml | 6 +++--- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/packages/pigeon/CHANGELOG.md b/packages/pigeon/CHANGELOG.md index 67675651294..4f8c47d2b84 100644 --- a/packages/pigeon/CHANGELOG.md +++ b/packages/pigeon/CHANGELOG.md @@ -1,3 +1,7 @@ +## 22.7.3 + +* Adds compatibility with `analyzer` 7.x.*. + ## 22.7.2 * Updates README to discuss best practices for using Pigeon-generated code. diff --git a/packages/pigeon/lib/dart_generator.dart b/packages/pigeon/lib/dart_generator.dart index abacf3dea31..4f54839f874 100644 --- a/packages/pigeon/lib/dart_generator.dart +++ b/packages/pigeon/lib/dart_generator.dart @@ -5,6 +5,7 @@ import 'package:code_builder/code_builder.dart' as cb; import 'package:dart_style/dart_style.dart'; import 'package:path/path.dart' as path; +import 'package:pub_semver/pub_semver.dart'; import 'ast.dart'; import 'dart/templates.dart'; @@ -779,7 +780,8 @@ final BinaryMessenger? ${varNamePrefix}binaryMessenger; final cb.DartEmitter emitter = cb.DartEmitter(useNullSafetySyntax: true); indent.format( - DartFormatter().format('${instanceManagerApi.accept(emitter)}'), + DartFormatter(languageVersion: Version(3, 6, 0)) + .format('${instanceManagerApi.accept(emitter)}'), ); } @@ -899,7 +901,8 @@ final BinaryMessenger? ${varNamePrefix}binaryMessenger; ); final cb.DartEmitter emitter = cb.DartEmitter(useNullSafetySyntax: true); - indent.format(DartFormatter().format('${proxyApi.accept(emitter)}')); + indent.format(DartFormatter(languageVersion: Version(3, 6, 0)) + .format('${proxyApi.accept(emitter)}')); } /// Generates Dart source code for test support libraries based on the given AST diff --git a/packages/pigeon/lib/generator_tools.dart b/packages/pigeon/lib/generator_tools.dart index 295d223fada..a64742711cb 100644 --- a/packages/pigeon/lib/generator_tools.dart +++ b/packages/pigeon/lib/generator_tools.dart @@ -14,7 +14,7 @@ import 'ast.dart'; /// The current version of pigeon. /// /// This must match the version in pubspec.yaml. -const String pigeonVersion = '22.7.2'; +const String pigeonVersion = '22.7.3'; /// Read all the content from [stdin] to a String. String readStdin() { diff --git a/packages/pigeon/pubspec.yaml b/packages/pigeon/pubspec.yaml index a4334a271a1..442e7194e9b 100644 --- a/packages/pigeon/pubspec.yaml +++ b/packages/pigeon/pubspec.yaml @@ -2,17 +2,17 @@ name: pigeon description: Code generator tool to make communication between Flutter and the host platform type-safe and easier. repository: https://github.com/flutter/packages/tree/main/packages/pigeon issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+pigeon%22 -version: 22.7.2 # This must match the version in lib/generator_tools.dart +version: 22.7.3 # This must match the version in lib/generator_tools.dart environment: sdk: ^3.4.0 dependencies: - analyzer: ">=5.13.0 <7.0.0" + analyzer: ">=6.0.0 <8.0.0" args: ^2.1.0 code_builder: ^4.10.0 collection: ^1.15.0 - dart_style: ^2.3.4 + dart_style: ^3.0.0 graphs: ^2.3.1 meta: ^1.9.0 path: ^1.8.0 From 6dee38164ca88c0399c671ca8d522e01df847f95 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Sun, 19 Jan 2025 19:40:42 -0500 Subject: [PATCH 8/9] Roll Flutter from 5517cc9b3b3b to b9e86a565f92 (22 revisions) (#8458) https://github.com/flutter/flutter/compare/5517cc9b3b3b...b9e86a565f92 2025-01-18 737941+loic-sharma@users.noreply.github.com Fix link to Linux custom embedder artifacts (flutter/flutter#161699) 2025-01-18 matanlurey@users.noreply.github.com Remove tests, GLFW examples, and non-artifact builds from `linux_host_engine`. (flutter/flutter#161786) 2025-01-18 jonahwilliams@google.com [Impeller] when mips are disabled, also disable from sampler options. (flutter/flutter#161765) 2025-01-18 lukepighetti@gmail.com revert removing Twitter, retain BlueSky (flutter/flutter#161803) 2025-01-17 yjbanov@google.com fix reorderable_list_test.dart (flutter/flutter#161836) 2025-01-17 kevinjchisholm@google.com [Release] Update the cherry-pick process. (flutter/flutter#161771) 2025-01-17 victorsanniay@gmail.com Reland "#143249 Autocomplete options width" (flutter/flutter#161695) 2025-01-17 a-siva@users.noreply.github.com Roll Dart to Version 3.8.0-1.0.dev (flutter/flutter#161781) 2025-01-17 jonahwilliams@google.com [Impeller] use 3 fences to synchronize AHB swapchains (like we do for KHR). (flutter/flutter#161767) 2025-01-17 jonahwilliams@google.com [Impeller] remove Adreno denylist entries. (flutter/flutter#161740) 2025-01-17 robert.ancell@canonical.com Refactor event redispatching (flutter/flutter#161701) 2025-01-17 jonahwilliams@google.com [Impellerc] correctly pad arrays of vec3s in reflector. (flutter/flutter#161697) 2025-01-17 jason-simmons@users.noreply.github.com Initialize dartLoader.rootDirectories so the Web stack trace mapper can convert package source paths (flutter/flutter#160383) 2025-01-16 robert.ancell@canonical.com Make fl_keyboard_manager_handle_event async (flutter/flutter#161637) 2025-01-16 kevmoo@users.noreply.github.com Update social links in readme (flutter/flutter#161778) 2025-01-16 robert.ancell@canonical.com Remove some stray printf debugging (flutter/flutter#161706) 2025-01-16 58529443+srujzs@users.noreply.github.com Set meta tag in default index (flutter/flutter#161493) 2025-01-16 andrewrkolos@gmail.com remove usage of `Usage` from build system (flutter/flutter#160663) 2025-01-16 andrewrkolos@gmail.com route CLI command usage information through the logger instead of using `print` (flutter/flutter#161533) 2025-01-16 matanlurey@users.noreply.github.com Enable duplicate `linux_host_engine_test`. (flutter/flutter#161613) 2025-01-16 58190796+MitchellGoodwin@users.noreply.github.com Do not block vertical drag gestures in CupertinoSheetRoute body (flutter/flutter#161696) 2025-01-16 jonahwilliams@google.com [Impeller] Update partial repaint to use a fullsize onscreen. (flutter/flutter#161626) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/flutter-packages Please CC stuartmorgan@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Packages: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- .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 5c5d3b2938d..068fdfc40d2 100644 --- a/.ci/flutter_master.version +++ b/.ci/flutter_master.version @@ -1 +1 @@ -5517cc9b3b3bcf12431b47f495e342a30b738835 +b9e86a565f92adce9d97a8af8345468b7a54cdc7 From e8f1f63bc3b75a5bea33eb6b07226c4b140a24d0 Mon Sep 17 00:00:00 2001 From: Marcin Chudy Date: Mon, 20 Jan 2025 16:32:33 +0100 Subject: [PATCH 9/9] [in_app_purchase] Update Play Billing library to 7.1.1 (#8218) Updates Play Billing Library to the latest version 7.1.1. Exposes new APIs as per [release notes](https://developer.android.com/google/play/billing/release-notes): - Adds Dart representation of `ProductDetails.InstallmentPlanDetails` - Adds Dart representation of `PendingPurchasesParams` and removes the deprecated `enablePendingPurchases` method on `BillingClientWrapper` (breaking change) - Adds Dart representation of `Purchase.PendingPurchaseUpdate` - Removes the deprecated `ProrationMode` as it has been removed from the native library (breaking change) This PR introduces breaking changes in `in_app_purchase_android`, but does not introduce any breaking changes on the platform interface level. Fixes https://github.com/flutter/flutter/issues/147394 --- .../in_app_purchase_android/CHANGELOG.md | 11 + .../android/build.gradle | 4 +- .../inapppurchase/BillingClientFactory.java | 5 +- .../BillingClientFactoryImpl.java | 8 +- .../plugins/inapppurchase/Messages.java | 594 +++++++++++++++--- .../inapppurchase/MethodCallHandlerImpl.java | 62 +- .../plugins/inapppurchase/Translator.java | 123 +++- .../BillingClientFactoryImplTest.java | 21 +- .../inapppurchase/MethodCallHandlerTest.java | 229 ++++--- .../plugins/inapppurchase/TranslatorTest.java | 24 +- .../in_app_purchase_android/build.yaml | 8 - .../example/lib/main.dart | 28 +- ...illing_only_reporting_details_wrapper.dart | 27 - ...ling_only_reporting_details_wrapper.g.dart | 17 - .../billing_client_manager.dart | 27 +- .../billing_client_wrapper.dart | 311 ++------- .../billing_client_wrapper.g.dart | 61 -- .../billing_config_wrapper.dart | 26 - .../billing_config_wrapper.g.dart | 15 - .../billing_response_wrapper.dart | 23 - .../billing_response_wrapper.g.dart | 14 - ...e_time_purchase_offer_details_wrapper.dart | 18 - ...time_purchase_offer_details_wrapper.g.dart | 15 - .../pending_purchases_params_wrapper.dart | 39 ++ .../product_details_wrapper.dart | 59 -- .../product_details_wrapper.g.dart | 49 -- .../product_wrapper.dart | 11 - .../product_wrapper.g.dart | 23 - .../purchase_wrapper.dart | 146 ++--- .../purchase_wrapper.g.dart | 75 --- .../subscription_offer_details_wrapper.dart | 91 ++- .../subscription_offer_details_wrapper.g.dart | 37 -- .../user_choice_details_wrapper.dart | 34 - .../user_choice_details_wrapper.g.dart | 45 -- .../src/in_app_purchase_android_platform.dart | 1 - ...pp_purchase_android_platform_addition.dart | 26 - .../lib/src/messages.g.dart | 279 ++++++-- .../lib/src/pigeon_converters.dart | 156 ++++- .../src/types/change_subscription_param.dart | 8 - .../types/google_play_purchase_details.dart | 4 +- .../pigeons/messages.dart | 94 ++- .../in_app_purchase_android/pubspec.yaml | 4 +- .../billing_client_manager_test.dart | 47 +- .../billing_client_wrapper_test.dart | 193 ++---- .../billing_client_wrapper_test.mocks.dart | 8 +- .../product_details_wrapper_test.dart | 212 +------ .../product_wrapper_test.dart | 30 - .../purchase_wrapper_test.dart | 148 ----- ...rchase_android_platform_addition_test.dart | 24 +- ...in_app_purchase_android_platform_test.dart | 48 +- .../test/test_conversion_utils.dart | 3 +- 51 files changed, 1630 insertions(+), 1935 deletions(-) delete mode 100644 packages/in_app_purchase/in_app_purchase_android/build.yaml delete mode 100644 packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/alternative_billing_only_reporting_details_wrapper.g.dart delete mode 100644 packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_wrapper.g.dart delete mode 100644 packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_config_wrapper.g.dart delete mode 100644 packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_response_wrapper.g.dart delete mode 100644 packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/one_time_purchase_offer_details_wrapper.g.dart create mode 100644 packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/pending_purchases_params_wrapper.dart delete mode 100644 packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/product_details_wrapper.g.dart delete mode 100644 packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/product_wrapper.g.dart delete mode 100644 packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/purchase_wrapper.g.dart delete mode 100644 packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/subscription_offer_details_wrapper.g.dart delete mode 100644 packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/user_choice_details_wrapper.g.dart delete mode 100644 packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/product_wrapper_test.dart 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 36b7371e7b2..93c8c940108 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,14 @@ +## 0.4.0 + +* Updates Google Play Billing Library from 6.2.0 to 7.1.1. +* **BREAKING CHANGES**: + * Removes the deprecated `ProrationMode` enum. `ReplacementMode` should be used instead. + * Removes the deprecated `BillingClientWrapper.enablePendingPurchases` method. + * Removes JSON serialization from Dart wrapper classes. + * Removes `subscriptionsOnVR` and `inAppItemsOnVR` from `BillingClientFeature`. +* Adds `installmentPlanDetails` to `SubscriptionOfferDetailsWrapper`. +* Adds APIs to support pending transactions for subscription prepaid plans (`PendingPurchasesParams`). + ## 0.3.6+13 * Updates androidx.annotation:annotation to 1.9.1. 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 29e12ba9d8a..4539ca78454 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 @@ -31,7 +31,7 @@ android { compileSdk 34 defaultConfig { - minSdk 19 + minSdk 21 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } lintOptions { @@ -60,7 +60,7 @@ android { dependencies { implementation 'androidx.annotation:annotation:1.9.1' - implementation 'com.android.billingclient:billing:6.2.0' + implementation 'com.android.billingclient:billing:7.1.1' testImplementation 'junit:junit:4.13.2' testImplementation 'org.json:json:20250107' testImplementation 'org.mockito:mockito-core:5.4.0' diff --git a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/BillingClientFactory.java b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/BillingClientFactory.java index 979e54a37bb..7ce00627351 100644 --- a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/BillingClientFactory.java +++ b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/BillingClientFactory.java @@ -19,10 +19,13 @@ interface BillingClientFactory { * @param callbackApi The callback API to be used by the {@link BillingClient}. * @param billingChoiceMode Enables the ability to offer alternative billing or Google Play * billing. + * @param pendingPurchasesParams Parameters to enable pending purchases. See {@link + * com.android.billingclient.api.PendingPurchasesParams}. * @return The {@link BillingClient} object that is created. */ BillingClient createBillingClient( @NonNull Context context, @NonNull Messages.InAppPurchaseCallbackApi callbackApi, - PlatformBillingChoiceMode billingChoiceMode); + PlatformBillingChoiceMode billingChoiceMode, + Messages.PlatformPendingPurchasesParams pendingPurchasesParams); } diff --git a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/BillingClientFactoryImpl.java b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/BillingClientFactoryImpl.java index 460414b2e5b..5b7d6bbe403 100644 --- a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/BillingClientFactoryImpl.java +++ b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/BillingClientFactoryImpl.java @@ -5,6 +5,7 @@ package io.flutter.plugins.inapppurchase; import static io.flutter.plugins.inapppurchase.Translator.fromUserChoiceDetails; +import static io.flutter.plugins.inapppurchase.Translator.toPendingPurchasesParams; import android.content.Context; import androidx.annotation.NonNull; @@ -21,8 +22,11 @@ final class BillingClientFactoryImpl implements BillingClientFactory { public BillingClient createBillingClient( @NonNull Context context, @NonNull Messages.InAppPurchaseCallbackApi callbackApi, - PlatformBillingChoiceMode billingChoiceMode) { - BillingClient.Builder builder = BillingClient.newBuilder(context).enablePendingPurchases(); + PlatformBillingChoiceMode billingChoiceMode, + Messages.PlatformPendingPurchasesParams pendingPurchasesParams) { + BillingClient.Builder builder = + BillingClient.newBuilder(context) + .enablePendingPurchases(toPendingPurchasesParams(pendingPurchasesParams)); switch (billingChoiceMode) { case ALTERNATIVE_BILLING_ONLY: // https://developer.android.com/google/play/billing/alternative/alternative-billing-without-user-choice-in-app diff --git a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/Messages.java b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/Messages.java index cef3e316c3d..2738d4ffaf1 100644 --- a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/Messages.java +++ b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/Messages.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 (v22.4.2), do not edit directly. +// Autogenerated from Pigeon (v22.6.1), do not edit directly. // See also: https://pub.dev/packages/pigeon package io.flutter.plugins.inapppurchase; @@ -72,6 +72,44 @@ protected static FlutterError createConnectionError(@NonNull String channelName) @Retention(CLASS) @interface CanIgnoreReturnValue {} + /** Pigeon version of Java BillingClient.BillingResponseCode. */ + public enum PlatformBillingResponse { + SERVICE_TIMEOUT(0), + FEATURE_NOT_SUPPORTED(1), + SERVICE_DISCONNECTED(2), + OK(3), + USER_CANCELED(4), + SERVICE_UNAVAILABLE(5), + BILLING_UNAVAILABLE(6), + ITEM_UNAVAILABLE(7), + DEVELOPER_ERROR(8), + ERROR(9), + ITEM_ALREADY_OWNED(10), + ITEM_NOT_OWNED(11), + NETWORK_ERROR(12); + + final int index; + + PlatformBillingResponse(final int index) { + this.index = index; + } + } + + public enum PlatformReplacementMode { + UNKNOWN_REPLACEMENT_MODE(0), + WITH_TIME_PRORATION(1), + CHARGE_PRORATED_PRICE(2), + WITHOUT_PRORATION(3), + DEFERRED(4), + CHARGE_FULL_PRICE(5); + + final int index; + + PlatformReplacementMode(final int index) { + this.index = index; + } + } + /** Pigeon version of Java BillingClient.ProductType. */ public enum PlatformProductType { INAPP(0), @@ -104,6 +142,23 @@ public enum PlatformBillingChoiceMode { } } + public enum PlatformBillingClientFeature { + ALTERNATIVE_BILLING_ONLY(0), + BILLING_CONFIG(1), + EXTERNAL_OFFER(2), + IN_APP_MESSAGING(3), + PRICE_CHANGE_CONFIRMATION(4), + PRODUCT_DETAILS(5), + SUBSCRIPTIONS(6), + SUBSCRIPTIONS_UPDATE(7); + + final int index; + + PlatformBillingClientFeature(final int index) { + this.index = index; + } + } + /** Pigeon version of Java Purchase.PurchaseState. */ public enum PlatformPurchaseState { UNSPECIFIED(0), @@ -320,13 +375,13 @@ ArrayList toList() { *

Generated class from Pigeon that represents data sent in messages. */ public static final class PlatformBillingResult { - private @NonNull Long responseCode; + private @NonNull PlatformBillingResponse responseCode; - public @NonNull Long getResponseCode() { + public @NonNull PlatformBillingResponse getResponseCode() { return responseCode; } - public void setResponseCode(@NonNull Long setterArg) { + public void setResponseCode(@NonNull PlatformBillingResponse setterArg) { if (setterArg == null) { throw new IllegalStateException("Nonnull field \"responseCode\" is null."); } @@ -368,10 +423,10 @@ public int hashCode() { public static final class Builder { - private @Nullable Long responseCode; + private @Nullable PlatformBillingResponse responseCode; @CanIgnoreReturnValue - public @NonNull Builder setResponseCode(@NonNull Long setterArg) { + public @NonNull Builder setResponseCode(@NonNull PlatformBillingResponse setterArg) { this.responseCode = setterArg; return this; } @@ -403,7 +458,7 @@ ArrayList toList() { static @NonNull PlatformBillingResult fromList(@NonNull ArrayList pigeonVar_list) { PlatformBillingResult pigeonResult = new PlatformBillingResult(); Object responseCode = pigeonVar_list.get(0); - pigeonResult.setResponseCode((Long) responseCode); + pigeonResult.setResponseCode((PlatformBillingResponse) responseCode); Object debugMessage = pigeonVar_list.get(1); pigeonResult.setDebugMessage((String) debugMessage); return pigeonResult; @@ -1086,26 +1141,13 @@ public void setProduct(@NonNull String setterArg) { this.product = setterArg; } - private @NonNull Long prorationMode; - - public @NonNull Long getProrationMode() { - return prorationMode; - } - - public void setProrationMode(@NonNull Long setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"prorationMode\" is null."); - } - this.prorationMode = setterArg; - } - - private @NonNull Long replacementMode; + private @NonNull PlatformReplacementMode replacementMode; - public @NonNull Long getReplacementMode() { + public @NonNull PlatformReplacementMode getReplacementMode() { return replacementMode; } - public void setReplacementMode(@NonNull Long setterArg) { + public void setReplacementMode(@NonNull PlatformReplacementMode setterArg) { if (setterArg == null) { throw new IllegalStateException("Nonnull field \"replacementMode\" is null."); } @@ -1175,7 +1217,6 @@ public boolean equals(Object o) { } PlatformBillingFlowParams that = (PlatformBillingFlowParams) o; return product.equals(that.product) - && prorationMode.equals(that.prorationMode) && replacementMode.equals(that.replacementMode) && Objects.equals(offerToken, that.offerToken) && Objects.equals(accountId, that.accountId) @@ -1188,7 +1229,6 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash( product, - prorationMode, replacementMode, offerToken, accountId, @@ -1207,18 +1247,10 @@ public static final class Builder { return this; } - private @Nullable Long prorationMode; - - @CanIgnoreReturnValue - public @NonNull Builder setProrationMode(@NonNull Long setterArg) { - this.prorationMode = setterArg; - return this; - } - - private @Nullable Long replacementMode; + private @Nullable PlatformReplacementMode replacementMode; @CanIgnoreReturnValue - public @NonNull Builder setReplacementMode(@NonNull Long setterArg) { + public @NonNull Builder setReplacementMode(@NonNull PlatformReplacementMode setterArg) { this.replacementMode = setterArg; return this; } @@ -1266,7 +1298,6 @@ public static final class Builder { public @NonNull PlatformBillingFlowParams build() { PlatformBillingFlowParams pigeonReturn = new PlatformBillingFlowParams(); pigeonReturn.setProduct(product); - pigeonReturn.setProrationMode(prorationMode); pigeonReturn.setReplacementMode(replacementMode); pigeonReturn.setOfferToken(offerToken); pigeonReturn.setAccountId(accountId); @@ -1279,9 +1310,8 @@ public static final class Builder { @NonNull ArrayList toList() { - ArrayList toListResult = new ArrayList<>(8); + ArrayList toListResult = new ArrayList<>(7); toListResult.add(product); - toListResult.add(prorationMode); toListResult.add(replacementMode); toListResult.add(offerToken); toListResult.add(accountId); @@ -1295,19 +1325,17 @@ ArrayList toList() { PlatformBillingFlowParams pigeonResult = new PlatformBillingFlowParams(); Object product = pigeonVar_list.get(0); pigeonResult.setProduct((String) product); - Object prorationMode = pigeonVar_list.get(1); - pigeonResult.setProrationMode((Long) prorationMode); - Object replacementMode = pigeonVar_list.get(2); - pigeonResult.setReplacementMode((Long) replacementMode); - Object offerToken = pigeonVar_list.get(3); + Object replacementMode = pigeonVar_list.get(1); + pigeonResult.setReplacementMode((PlatformReplacementMode) replacementMode); + Object offerToken = pigeonVar_list.get(2); pigeonResult.setOfferToken((String) offerToken); - Object accountId = pigeonVar_list.get(4); + Object accountId = pigeonVar_list.get(3); pigeonResult.setAccountId((String) accountId); - Object obfuscatedProfileId = pigeonVar_list.get(5); + Object obfuscatedProfileId = pigeonVar_list.get(4); pigeonResult.setObfuscatedProfileId((String) obfuscatedProfileId); - Object oldProduct = pigeonVar_list.get(6); + Object oldProduct = pigeonVar_list.get(5); pigeonResult.setOldProduct((String) oldProduct); - Object purchaseToken = pigeonVar_list.get(7); + Object purchaseToken = pigeonVar_list.get(6); pigeonResult.setPurchaseToken((String) purchaseToken); return pigeonResult; } @@ -1691,6 +1719,16 @@ public void setAccountIdentifiers(@Nullable PlatformAccountIdentifiers setterArg this.accountIdentifiers = setterArg; } + private @Nullable PlatformPendingPurchaseUpdate pendingPurchaseUpdate; + + public @Nullable PlatformPendingPurchaseUpdate getPendingPurchaseUpdate() { + return pendingPurchaseUpdate; + } + + public void setPendingPurchaseUpdate(@Nullable PlatformPendingPurchaseUpdate setterArg) { + this.pendingPurchaseUpdate = setterArg; + } + /** Constructor is non-public to enforce null safety; use Builder. */ PlatformPurchase() {} @@ -1715,7 +1753,8 @@ public boolean equals(Object o) { && isAcknowledged.equals(that.isAcknowledged) && quantity.equals(that.quantity) && purchaseState.equals(that.purchaseState) - && Objects.equals(accountIdentifiers, that.accountIdentifiers); + && Objects.equals(accountIdentifiers, that.accountIdentifiers) + && Objects.equals(pendingPurchaseUpdate, that.pendingPurchaseUpdate); } @Override @@ -1733,7 +1772,8 @@ public int hashCode() { isAcknowledged, quantity, purchaseState, - accountIdentifiers); + accountIdentifiers, + pendingPurchaseUpdate); } public static final class Builder { @@ -1843,6 +1883,15 @@ public static final class Builder { return this; } + private @Nullable PlatformPendingPurchaseUpdate pendingPurchaseUpdate; + + @CanIgnoreReturnValue + public @NonNull Builder setPendingPurchaseUpdate( + @Nullable PlatformPendingPurchaseUpdate setterArg) { + this.pendingPurchaseUpdate = setterArg; + return this; + } + public @NonNull PlatformPurchase build() { PlatformPurchase pigeonReturn = new PlatformPurchase(); pigeonReturn.setOrderId(orderId); @@ -1858,13 +1907,14 @@ public static final class Builder { pigeonReturn.setQuantity(quantity); pigeonReturn.setPurchaseState(purchaseState); pigeonReturn.setAccountIdentifiers(accountIdentifiers); + pigeonReturn.setPendingPurchaseUpdate(pendingPurchaseUpdate); return pigeonReturn; } } @NonNull ArrayList toList() { - ArrayList toListResult = new ArrayList<>(13); + ArrayList toListResult = new ArrayList<>(14); toListResult.add(orderId); toListResult.add(packageName); toListResult.add(purchaseTime); @@ -1878,6 +1928,7 @@ ArrayList toList() { toListResult.add(quantity); toListResult.add(purchaseState); toListResult.add(accountIdentifiers); + toListResult.add(pendingPurchaseUpdate); return toListResult; } @@ -1909,6 +1960,107 @@ ArrayList toList() { pigeonResult.setPurchaseState((PlatformPurchaseState) purchaseState); Object accountIdentifiers = pigeonVar_list.get(12); pigeonResult.setAccountIdentifiers((PlatformAccountIdentifiers) accountIdentifiers); + Object pendingPurchaseUpdate = pigeonVar_list.get(13); + pigeonResult.setPendingPurchaseUpdate((PlatformPendingPurchaseUpdate) pendingPurchaseUpdate); + return pigeonResult; + } + } + + /** + * Pigeon version of Java Purchase. + * + *

See also PendingPurchaseUpdateWrapper on the Dart side. + * + *

Generated class from Pigeon that represents data sent in messages. + */ + public static final class PlatformPendingPurchaseUpdate { + private @NonNull List products; + + public @NonNull List getProducts() { + return products; + } + + public void setProducts(@NonNull List setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"products\" is null."); + } + this.products = setterArg; + } + + private @NonNull String purchaseToken; + + public @NonNull String getPurchaseToken() { + return purchaseToken; + } + + public void setPurchaseToken(@NonNull String setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"purchaseToken\" is null."); + } + this.purchaseToken = setterArg; + } + + /** Constructor is non-public to enforce null safety; use Builder. */ + PlatformPendingPurchaseUpdate() {} + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + PlatformPendingPurchaseUpdate that = (PlatformPendingPurchaseUpdate) o; + return products.equals(that.products) && purchaseToken.equals(that.purchaseToken); + } + + @Override + public int hashCode() { + return Objects.hash(products, purchaseToken); + } + + public static final class Builder { + + private @Nullable List products; + + @CanIgnoreReturnValue + public @NonNull Builder setProducts(@NonNull List setterArg) { + this.products = setterArg; + return this; + } + + private @Nullable String purchaseToken; + + @CanIgnoreReturnValue + public @NonNull Builder setPurchaseToken(@NonNull String setterArg) { + this.purchaseToken = setterArg; + return this; + } + + public @NonNull PlatformPendingPurchaseUpdate build() { + PlatformPendingPurchaseUpdate pigeonReturn = new PlatformPendingPurchaseUpdate(); + pigeonReturn.setProducts(products); + pigeonReturn.setPurchaseToken(purchaseToken); + return pigeonReturn; + } + } + + @NonNull + ArrayList toList() { + ArrayList toListResult = new ArrayList<>(2); + toListResult.add(products); + toListResult.add(purchaseToken); + return toListResult; + } + + static @NonNull PlatformPendingPurchaseUpdate fromList( + @NonNull ArrayList pigeonVar_list) { + PlatformPendingPurchaseUpdate pigeonResult = new PlatformPendingPurchaseUpdate(); + Object products = pigeonVar_list.get(0); + pigeonResult.setProducts((List) products); + Object purchaseToken = pigeonVar_list.get(1); + pigeonResult.setPurchaseToken((String) purchaseToken); return pigeonResult; } } @@ -2410,6 +2562,16 @@ public void setPricingPhases(@NonNull List setterArg) { this.pricingPhases = setterArg; } + private @Nullable PlatformInstallmentPlanDetails installmentPlanDetails; + + public @Nullable PlatformInstallmentPlanDetails getInstallmentPlanDetails() { + return installmentPlanDetails; + } + + public void setInstallmentPlanDetails(@Nullable PlatformInstallmentPlanDetails setterArg) { + this.installmentPlanDetails = setterArg; + } + /** Constructor is non-public to enforce null safety; use Builder. */ PlatformSubscriptionOfferDetails() {} @@ -2426,12 +2588,14 @@ public boolean equals(Object o) { && Objects.equals(offerId, that.offerId) && offerToken.equals(that.offerToken) && offerTags.equals(that.offerTags) - && pricingPhases.equals(that.pricingPhases); + && pricingPhases.equals(that.pricingPhases) + && Objects.equals(installmentPlanDetails, that.installmentPlanDetails); } @Override public int hashCode() { - return Objects.hash(basePlanId, offerId, offerToken, offerTags, pricingPhases); + return Objects.hash( + basePlanId, offerId, offerToken, offerTags, pricingPhases, installmentPlanDetails); } public static final class Builder { @@ -2476,6 +2640,15 @@ public static final class Builder { return this; } + private @Nullable PlatformInstallmentPlanDetails installmentPlanDetails; + + @CanIgnoreReturnValue + public @NonNull Builder setInstallmentPlanDetails( + @Nullable PlatformInstallmentPlanDetails setterArg) { + this.installmentPlanDetails = setterArg; + return this; + } + public @NonNull PlatformSubscriptionOfferDetails build() { PlatformSubscriptionOfferDetails pigeonReturn = new PlatformSubscriptionOfferDetails(); pigeonReturn.setBasePlanId(basePlanId); @@ -2483,18 +2656,20 @@ public static final class Builder { pigeonReturn.setOfferToken(offerToken); pigeonReturn.setOfferTags(offerTags); pigeonReturn.setPricingPhases(pricingPhases); + pigeonReturn.setInstallmentPlanDetails(installmentPlanDetails); return pigeonReturn; } } @NonNull ArrayList toList() { - ArrayList toListResult = new ArrayList<>(5); + ArrayList toListResult = new ArrayList<>(6); toListResult.add(basePlanId); toListResult.add(offerId); toListResult.add(offerToken); toListResult.add(offerTags); toListResult.add(pricingPhases); + toListResult.add(installmentPlanDetails); return toListResult; } @@ -2511,6 +2686,9 @@ ArrayList toList() { pigeonResult.setOfferTags((List) offerTags); Object pricingPhases = pigeonVar_list.get(4); pigeonResult.setPricingPhases((List) pricingPhases); + Object installmentPlanDetails = pigeonVar_list.get(5); + pigeonResult.setInstallmentPlanDetails( + (PlatformInstallmentPlanDetails) installmentPlanDetails); return pigeonResult; } } @@ -2755,6 +2933,178 @@ ArrayList toList() { } } + /** + * Pigeon version of ProductDetails.InstallmentPlanDetails. + * https://developer.android.com/reference/com/android/billingclient/api/PendingPurchasesParams.Builder#enableOneTimeProducts() + * + *

Generated class from Pigeon that represents data sent in messages. + */ + public static final class PlatformInstallmentPlanDetails { + private @NonNull Long commitmentPaymentsCount; + + public @NonNull Long getCommitmentPaymentsCount() { + return commitmentPaymentsCount; + } + + public void setCommitmentPaymentsCount(@NonNull Long setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"commitmentPaymentsCount\" is null."); + } + this.commitmentPaymentsCount = setterArg; + } + + private @NonNull Long subsequentCommitmentPaymentsCount; + + public @NonNull Long getSubsequentCommitmentPaymentsCount() { + return subsequentCommitmentPaymentsCount; + } + + public void setSubsequentCommitmentPaymentsCount(@NonNull Long setterArg) { + if (setterArg == null) { + throw new IllegalStateException( + "Nonnull field \"subsequentCommitmentPaymentsCount\" is null."); + } + this.subsequentCommitmentPaymentsCount = setterArg; + } + + /** Constructor is non-public to enforce null safety; use Builder. */ + PlatformInstallmentPlanDetails() {} + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + PlatformInstallmentPlanDetails that = (PlatformInstallmentPlanDetails) o; + return commitmentPaymentsCount.equals(that.commitmentPaymentsCount) + && subsequentCommitmentPaymentsCount.equals(that.subsequentCommitmentPaymentsCount); + } + + @Override + public int hashCode() { + return Objects.hash(commitmentPaymentsCount, subsequentCommitmentPaymentsCount); + } + + public static final class Builder { + + private @Nullable Long commitmentPaymentsCount; + + @CanIgnoreReturnValue + public @NonNull Builder setCommitmentPaymentsCount(@NonNull Long setterArg) { + this.commitmentPaymentsCount = setterArg; + return this; + } + + private @Nullable Long subsequentCommitmentPaymentsCount; + + @CanIgnoreReturnValue + public @NonNull Builder setSubsequentCommitmentPaymentsCount(@NonNull Long setterArg) { + this.subsequentCommitmentPaymentsCount = setterArg; + return this; + } + + public @NonNull PlatformInstallmentPlanDetails build() { + PlatformInstallmentPlanDetails pigeonReturn = new PlatformInstallmentPlanDetails(); + pigeonReturn.setCommitmentPaymentsCount(commitmentPaymentsCount); + pigeonReturn.setSubsequentCommitmentPaymentsCount(subsequentCommitmentPaymentsCount); + return pigeonReturn; + } + } + + @NonNull + ArrayList toList() { + ArrayList toListResult = new ArrayList<>(2); + toListResult.add(commitmentPaymentsCount); + toListResult.add(subsequentCommitmentPaymentsCount); + return toListResult; + } + + static @NonNull PlatformInstallmentPlanDetails fromList( + @NonNull ArrayList pigeonVar_list) { + PlatformInstallmentPlanDetails pigeonResult = new PlatformInstallmentPlanDetails(); + Object commitmentPaymentsCount = pigeonVar_list.get(0); + pigeonResult.setCommitmentPaymentsCount((Long) commitmentPaymentsCount); + Object subsequentCommitmentPaymentsCount = pigeonVar_list.get(1); + pigeonResult.setSubsequentCommitmentPaymentsCount((Long) subsequentCommitmentPaymentsCount); + return pigeonResult; + } + } + + /** + * Pigeon version of Java PendingPurchasesParams. + * + *

Generated class from Pigeon that represents data sent in messages. + */ + public static final class PlatformPendingPurchasesParams { + private @NonNull Boolean enablePrepaidPlans; + + public @NonNull Boolean getEnablePrepaidPlans() { + return enablePrepaidPlans; + } + + public void setEnablePrepaidPlans(@NonNull Boolean setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"enablePrepaidPlans\" is null."); + } + this.enablePrepaidPlans = setterArg; + } + + /** Constructor is non-public to enforce null safety; use Builder. */ + PlatformPendingPurchasesParams() {} + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + PlatformPendingPurchasesParams that = (PlatformPendingPurchasesParams) o; + return enablePrepaidPlans.equals(that.enablePrepaidPlans); + } + + @Override + public int hashCode() { + return Objects.hash(enablePrepaidPlans); + } + + public static final class Builder { + + private @Nullable Boolean enablePrepaidPlans; + + @CanIgnoreReturnValue + public @NonNull Builder setEnablePrepaidPlans(@NonNull Boolean setterArg) { + this.enablePrepaidPlans = setterArg; + return this; + } + + public @NonNull PlatformPendingPurchasesParams build() { + PlatformPendingPurchasesParams pigeonReturn = new PlatformPendingPurchasesParams(); + pigeonReturn.setEnablePrepaidPlans(enablePrepaidPlans); + return pigeonReturn; + } + } + + @NonNull + ArrayList toList() { + ArrayList toListResult = new ArrayList<>(1); + toListResult.add(enablePrepaidPlans); + return toListResult; + } + + static @NonNull PlatformPendingPurchasesParams fromList( + @NonNull ArrayList pigeonVar_list) { + PlatformPendingPurchasesParams pigeonResult = new PlatformPendingPurchasesParams(); + Object enablePrepaidPlans = pigeonVar_list.get(0); + pigeonResult.setEnablePrepaidPlans((Boolean) enablePrepaidPlans); + return pigeonResult; + } + } + private static class PigeonCodec extends StandardMessageCodec { public static final PigeonCodec INSTANCE = new PigeonCodec(); @@ -2766,63 +3116,90 @@ protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) { case (byte) 129: { Object value = readValue(buffer); - return value == null ? null : PlatformProductType.values()[((Long) value).intValue()]; + return value == null + ? null + : PlatformBillingResponse.values()[((Long) value).intValue()]; } case (byte) 130: { Object value = readValue(buffer); return value == null ? null - : PlatformBillingChoiceMode.values()[((Long) value).intValue()]; + : PlatformReplacementMode.values()[((Long) value).intValue()]; } case (byte) 131: { Object value = readValue(buffer); - return value == null ? null : PlatformPurchaseState.values()[((Long) value).intValue()]; + return value == null ? null : PlatformProductType.values()[((Long) value).intValue()]; } case (byte) 132: { Object value = readValue(buffer); return value == null ? null - : PlatformRecurrenceMode.values()[((Long) value).intValue()]; + : PlatformBillingChoiceMode.values()[((Long) value).intValue()]; } case (byte) 133: - return PlatformQueryProduct.fromList((ArrayList) readValue(buffer)); + { + Object value = readValue(buffer); + return value == null + ? null + : PlatformBillingClientFeature.values()[((Long) value).intValue()]; + } case (byte) 134: - return PlatformAccountIdentifiers.fromList((ArrayList) readValue(buffer)); + { + Object value = readValue(buffer); + return value == null ? null : PlatformPurchaseState.values()[((Long) value).intValue()]; + } case (byte) 135: - return PlatformBillingResult.fromList((ArrayList) readValue(buffer)); + { + Object value = readValue(buffer); + return value == null + ? null + : PlatformRecurrenceMode.values()[((Long) value).intValue()]; + } case (byte) 136: + return PlatformQueryProduct.fromList((ArrayList) readValue(buffer)); + case (byte) 137: + return PlatformAccountIdentifiers.fromList((ArrayList) readValue(buffer)); + case (byte) 138: + return PlatformBillingResult.fromList((ArrayList) readValue(buffer)); + case (byte) 139: return PlatformOneTimePurchaseOfferDetails.fromList( (ArrayList) readValue(buffer)); - case (byte) 137: + case (byte) 140: return PlatformProductDetails.fromList((ArrayList) readValue(buffer)); - case (byte) 138: + case (byte) 141: return PlatformProductDetailsResponse.fromList((ArrayList) readValue(buffer)); - case (byte) 139: + case (byte) 142: return PlatformAlternativeBillingOnlyReportingDetailsResponse.fromList( (ArrayList) readValue(buffer)); - case (byte) 140: + case (byte) 143: return PlatformBillingConfigResponse.fromList((ArrayList) readValue(buffer)); - case (byte) 141: + case (byte) 144: return PlatformBillingFlowParams.fromList((ArrayList) readValue(buffer)); - case (byte) 142: + case (byte) 145: return PlatformPricingPhase.fromList((ArrayList) readValue(buffer)); - case (byte) 143: + case (byte) 146: return PlatformPurchase.fromList((ArrayList) readValue(buffer)); - case (byte) 144: + case (byte) 147: + return PlatformPendingPurchaseUpdate.fromList((ArrayList) readValue(buffer)); + case (byte) 148: return PlatformPurchaseHistoryRecord.fromList((ArrayList) readValue(buffer)); - case (byte) 145: + case (byte) 149: return PlatformPurchaseHistoryResponse.fromList((ArrayList) readValue(buffer)); - case (byte) 146: + case (byte) 150: return PlatformPurchasesResponse.fromList((ArrayList) readValue(buffer)); - case (byte) 147: + case (byte) 151: return PlatformSubscriptionOfferDetails.fromList((ArrayList) readValue(buffer)); - case (byte) 148: + case (byte) 152: return PlatformUserChoiceDetails.fromList((ArrayList) readValue(buffer)); - case (byte) 149: + case (byte) 153: return PlatformUserChoiceProduct.fromList((ArrayList) readValue(buffer)); + case (byte) 154: + return PlatformInstallmentPlanDetails.fromList((ArrayList) readValue(buffer)); + case (byte) 155: + return PlatformPendingPurchasesParams.fromList((ArrayList) readValue(buffer)); default: return super.readValueOfType(type, buffer); } @@ -2830,70 +3207,88 @@ protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) { @Override protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) { - if (value instanceof PlatformProductType) { + if (value instanceof PlatformBillingResponse) { stream.write(129); + writeValue(stream, value == null ? null : ((PlatformBillingResponse) value).index); + } else if (value instanceof PlatformReplacementMode) { + stream.write(130); + writeValue(stream, value == null ? null : ((PlatformReplacementMode) value).index); + } else if (value instanceof PlatformProductType) { + stream.write(131); writeValue(stream, value == null ? null : ((PlatformProductType) value).index); } else if (value instanceof PlatformBillingChoiceMode) { - stream.write(130); + stream.write(132); writeValue(stream, value == null ? null : ((PlatformBillingChoiceMode) value).index); + } else if (value instanceof PlatformBillingClientFeature) { + stream.write(133); + writeValue(stream, value == null ? null : ((PlatformBillingClientFeature) value).index); } else if (value instanceof PlatformPurchaseState) { - stream.write(131); + stream.write(134); writeValue(stream, value == null ? null : ((PlatformPurchaseState) value).index); } else if (value instanceof PlatformRecurrenceMode) { - stream.write(132); + stream.write(135); writeValue(stream, value == null ? null : ((PlatformRecurrenceMode) value).index); } else if (value instanceof PlatformQueryProduct) { - stream.write(133); + stream.write(136); writeValue(stream, ((PlatformQueryProduct) value).toList()); } else if (value instanceof PlatformAccountIdentifiers) { - stream.write(134); + stream.write(137); writeValue(stream, ((PlatformAccountIdentifiers) value).toList()); } else if (value instanceof PlatformBillingResult) { - stream.write(135); + stream.write(138); writeValue(stream, ((PlatformBillingResult) value).toList()); } else if (value instanceof PlatformOneTimePurchaseOfferDetails) { - stream.write(136); + stream.write(139); writeValue(stream, ((PlatformOneTimePurchaseOfferDetails) value).toList()); } else if (value instanceof PlatformProductDetails) { - stream.write(137); + stream.write(140); writeValue(stream, ((PlatformProductDetails) value).toList()); } else if (value instanceof PlatformProductDetailsResponse) { - stream.write(138); + stream.write(141); writeValue(stream, ((PlatformProductDetailsResponse) value).toList()); } else if (value instanceof PlatformAlternativeBillingOnlyReportingDetailsResponse) { - stream.write(139); + stream.write(142); writeValue( stream, ((PlatformAlternativeBillingOnlyReportingDetailsResponse) value).toList()); } else if (value instanceof PlatformBillingConfigResponse) { - stream.write(140); + stream.write(143); writeValue(stream, ((PlatformBillingConfigResponse) value).toList()); } else if (value instanceof PlatformBillingFlowParams) { - stream.write(141); + stream.write(144); writeValue(stream, ((PlatformBillingFlowParams) value).toList()); } else if (value instanceof PlatformPricingPhase) { - stream.write(142); + stream.write(145); writeValue(stream, ((PlatformPricingPhase) value).toList()); } else if (value instanceof PlatformPurchase) { - stream.write(143); + stream.write(146); writeValue(stream, ((PlatformPurchase) value).toList()); + } else if (value instanceof PlatformPendingPurchaseUpdate) { + stream.write(147); + writeValue(stream, ((PlatformPendingPurchaseUpdate) value).toList()); } else if (value instanceof PlatformPurchaseHistoryRecord) { - stream.write(144); + stream.write(148); writeValue(stream, ((PlatformPurchaseHistoryRecord) value).toList()); } else if (value instanceof PlatformPurchaseHistoryResponse) { - stream.write(145); + stream.write(149); writeValue(stream, ((PlatformPurchaseHistoryResponse) value).toList()); } else if (value instanceof PlatformPurchasesResponse) { - stream.write(146); + stream.write(150); writeValue(stream, ((PlatformPurchasesResponse) value).toList()); } else if (value instanceof PlatformSubscriptionOfferDetails) { - stream.write(147); + stream.write(151); writeValue(stream, ((PlatformSubscriptionOfferDetails) value).toList()); } else if (value instanceof PlatformUserChoiceDetails) { - stream.write(148); + stream.write(152); writeValue(stream, ((PlatformUserChoiceDetails) value).toList()); } else if (value instanceof PlatformUserChoiceProduct) { - stream.write(149); + stream.write(153); writeValue(stream, ((PlatformUserChoiceProduct) value).toList()); + } else if (value instanceof PlatformInstallmentPlanDetails) { + stream.write(154); + writeValue(stream, ((PlatformInstallmentPlanDetails) value).toList()); + } else if (value instanceof PlatformPendingPurchasesParams) { + stream.write(155); + writeValue(stream, ((PlatformPendingPurchasesParams) value).toList()); } else { super.writeValue(stream, value); } @@ -2933,6 +3328,7 @@ public interface InAppPurchaseApi { void startConnection( @NonNull Long callbackHandle, @NonNull PlatformBillingChoiceMode billingMode, + @NonNull PlatformPendingPurchasesParams pendingPurchasesParams, @NonNull Result result); /** Wraps BillingClient#endConnection(BillingClientStateListener). */ void endConnection(); @@ -2972,7 +3368,7 @@ void queryProductDetailsAsync( @NonNull Result result); /** Wraps BillingClient#isFeatureSupported(String). */ @NonNull - Boolean isFeatureSupported(@NonNull String feature); + Boolean isFeatureSupported(@NonNull PlatformBillingClientFeature feature); /** Wraps BillingClient#isAlternativeBillingOnlyAvailableAsync(). */ void isAlternativeBillingOnlyAvailableAsync(@NonNull Result result); /** Wraps BillingClient#showAlternativeBillingOnlyInformationDialog(). */ @@ -3037,6 +3433,8 @@ static void setUp( ArrayList args = (ArrayList) message; Long callbackHandleArg = (Long) args.get(0); PlatformBillingChoiceMode billingModeArg = (PlatformBillingChoiceMode) args.get(1); + PlatformPendingPurchasesParams pendingPurchasesParamsArg = + (PlatformPendingPurchasesParams) args.get(2); Result resultCallback = new Result() { public void success(PlatformBillingResult result) { @@ -3050,7 +3448,8 @@ public void error(Throwable error) { } }; - api.startConnection(callbackHandleArg, billingModeArg, resultCallback); + api.startConnection( + callbackHandleArg, billingModeArg, pendingPurchasesParamsArg, resultCallback); }); } else { channel.setMessageHandler(null); @@ -3306,7 +3705,8 @@ public void error(Throwable error) { (message, reply) -> { ArrayList wrapped = new ArrayList<>(); ArrayList args = (ArrayList) message; - String featureArg = (String) args.get(0); + PlatformBillingClientFeature featureArg = + (PlatformBillingClientFeature) args.get(0); try { Boolean output = api.isFeatureSupported(featureArg); wrapped.add(0, output); diff --git a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/MethodCallHandlerImpl.java b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/MethodCallHandlerImpl.java index c7305a69938..9949977dccb 100644 --- a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/MethodCallHandlerImpl.java +++ b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/MethodCallHandlerImpl.java @@ -10,8 +10,10 @@ import static io.flutter.plugins.inapppurchase.Translator.fromProductDetailsList; import static io.flutter.plugins.inapppurchase.Translator.fromPurchaseHistoryRecordList; import static io.flutter.plugins.inapppurchase.Translator.fromPurchasesList; +import static io.flutter.plugins.inapppurchase.Translator.toBillingClientFeature; import static io.flutter.plugins.inapppurchase.Translator.toProductList; import static io.flutter.plugins.inapppurchase.Translator.toProductTypeString; +import static io.flutter.plugins.inapppurchase.Translator.toReplacementMode; import android.app.Activity; import android.app.Application; @@ -37,6 +39,7 @@ import io.flutter.plugins.inapppurchase.Messages.InAppPurchaseApi; import io.flutter.plugins.inapppurchase.Messages.InAppPurchaseCallbackApi; import io.flutter.plugins.inapppurchase.Messages.PlatformBillingChoiceMode; +import io.flutter.plugins.inapppurchase.Messages.PlatformBillingClientFeature; import io.flutter.plugins.inapppurchase.Messages.PlatformBillingFlowParams; import io.flutter.plugins.inapppurchase.Messages.PlatformBillingResult; import io.flutter.plugins.inapppurchase.Messages.PlatformProductDetailsResponse; @@ -44,6 +47,7 @@ import io.flutter.plugins.inapppurchase.Messages.PlatformPurchaseHistoryResponse; import io.flutter.plugins.inapppurchase.Messages.PlatformPurchasesResponse; import io.flutter.plugins.inapppurchase.Messages.PlatformQueryProduct; +import io.flutter.plugins.inapppurchase.Messages.PlatformReplacementMode; import io.flutter.plugins.inapppurchase.Messages.Result; import java.util.ArrayList; import java.util.HashMap; @@ -51,19 +55,10 @@ /** Handles method channel for the plugin. */ class MethodCallHandlerImpl implements Application.ActivityLifecycleCallbacks, InAppPurchaseApi { - // TODO(gmackall): Replace uses of deprecated ProrationMode enum values with new - // ReplacementMode enum values. - // https://github.com/flutter/flutter/issues/128957. - @SuppressWarnings(value = "deprecation") @VisibleForTesting - static final int PRORATION_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY = - com.android.billingclient.api.BillingFlowParams.ProrationMode - .UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY; - - @VisibleForTesting - static final int REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY = - com.android.billingclient.api.BillingFlowParams.SubscriptionUpdateParams.ReplacementMode - .UNKNOWN_REPLACEMENT_MODE; + static final PlatformReplacementMode + REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY = + PlatformReplacementMode.UNKNOWN_REPLACEMENT_MODE; private static final String TAG = "InAppPurchasePlugin"; private static final String LOAD_PRODUCT_DOC_URL = @@ -290,23 +285,12 @@ public void queryProductDetailsAsync( } } - if (params.getProrationMode() != PRORATION_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY - && params.getReplacementMode() - != REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY) { - throw new FlutterError( - "IN_APP_PURCHASE_CONFLICT_PRORATION_MODE_REPLACEMENT_MODE", - "launchBillingFlow failed because you provided both prorationMode and replacementMode. You can only provide one of them.", - null); - } - if (params.getOldProduct() == null - && (params.getProrationMode() - != PRORATION_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY - || params.getReplacementMode() - != REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY)) { + && (params.getReplacementMode() + != REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY)) { throw new FlutterError( "IN_APP_PURCHASE_REQUIRE_OLD_PRODUCT", - "launchBillingFlow failed because oldProduct is null. You must provide a valid oldProduct in order to use a proration mode.", + "launchBillingFlow failed because oldProduct is null. You must provide a valid oldProduct in order to use a replacement mode.", null); } else if (params.getOldProduct() != null && !cachedProducts.containsKey(params.getOldProduct())) { @@ -352,31 +336,16 @@ public void queryProductDetailsAsync( && !params.getOldProduct().isEmpty() && params.getPurchaseToken() != null) { subscriptionUpdateParamsBuilder.setOldPurchaseToken(params.getPurchaseToken()); - if (params.getProrationMode() - != PRORATION_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY) { - setReplaceProrationMode( - subscriptionUpdateParamsBuilder, params.getProrationMode().intValue()); - } if (params.getReplacementMode() != REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY) { subscriptionUpdateParamsBuilder.setSubscriptionReplacementMode( - params.getReplacementMode().intValue()); + toReplacementMode(params.getReplacementMode())); } paramsBuilder.setSubscriptionUpdateParams(subscriptionUpdateParamsBuilder.build()); } return fromBillingResult(billingClient.launchBillingFlow(activity, paramsBuilder.build())); } - // TODO(gmackall): Replace uses of deprecated setReplaceProrationMode. - // https://github.com/flutter/flutter/issues/128957. - @SuppressWarnings(value = "deprecation") - private void setReplaceProrationMode( - BillingFlowParams.SubscriptionUpdateParams.Builder builder, int prorationMode) { - // The proration mode value has to match one of the following declared in - // https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.ProrationMode - builder.setReplaceProrationMode(prorationMode); - } - @Override public void consumeAsync( @NonNull String purchaseToken, @NonNull Result result) { @@ -428,6 +397,7 @@ public void queryPurchasesAsync( } @Override + @Deprecated public void queryPurchaseHistoryAsync( @NonNull PlatformProductType productType, @NonNull Result result) { @@ -457,10 +427,12 @@ public void queryPurchaseHistoryAsync( public void startConnection( @NonNull Long handle, @NonNull PlatformBillingChoiceMode billingMode, + @NonNull Messages.PlatformPendingPurchasesParams pendingPurchasesParams, @NonNull Result result) { if (billingClient == null) { billingClient = - billingClientFactory.createBillingClient(applicationContext, callbackApi, billingMode); + billingClientFactory.createBillingClient( + applicationContext, callbackApi, billingMode, pendingPurchasesParams); } try { @@ -534,11 +506,11 @@ protected void updateCachedProducts(@Nullable List productDetail } @Override - public @NonNull Boolean isFeatureSupported(@NonNull String feature) { + public @NonNull Boolean isFeatureSupported(@NonNull PlatformBillingClientFeature feature) { if (billingClient == null) { throw getNullBillingClientError(); } - BillingResult billingResult = billingClient.isFeatureSupported(feature); + BillingResult billingResult = billingClient.isFeatureSupported(toBillingClientFeature(feature)); return billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK; } } diff --git a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/Translator.java b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/Translator.java index c06b3acfb50..4fc1b115ec1 100644 --- a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/Translator.java +++ b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/Translator.java @@ -10,7 +10,9 @@ import com.android.billingclient.api.AlternativeBillingOnlyReportingDetails; import com.android.billingclient.api.BillingClient; import com.android.billingclient.api.BillingConfig; +import com.android.billingclient.api.BillingFlowParams; import com.android.billingclient.api.BillingResult; +import com.android.billingclient.api.PendingPurchasesParams; import com.android.billingclient.api.ProductDetails; import com.android.billingclient.api.Purchase; import com.android.billingclient.api.PurchaseHistoryRecord; @@ -19,9 +21,12 @@ import io.flutter.plugins.inapppurchase.Messages.FlutterError; import io.flutter.plugins.inapppurchase.Messages.PlatformAccountIdentifiers; import io.flutter.plugins.inapppurchase.Messages.PlatformAlternativeBillingOnlyReportingDetailsResponse; +import io.flutter.plugins.inapppurchase.Messages.PlatformBillingClientFeature; import io.flutter.plugins.inapppurchase.Messages.PlatformBillingConfigResponse; +import io.flutter.plugins.inapppurchase.Messages.PlatformBillingResponse; import io.flutter.plugins.inapppurchase.Messages.PlatformBillingResult; import io.flutter.plugins.inapppurchase.Messages.PlatformOneTimePurchaseOfferDetails; +import io.flutter.plugins.inapppurchase.Messages.PlatformPendingPurchaseUpdate; import io.flutter.plugins.inapppurchase.Messages.PlatformPricingPhase; import io.flutter.plugins.inapppurchase.Messages.PlatformProductDetails; import io.flutter.plugins.inapppurchase.Messages.PlatformProductType; @@ -30,6 +35,7 @@ import io.flutter.plugins.inapppurchase.Messages.PlatformPurchaseState; import io.flutter.plugins.inapppurchase.Messages.PlatformQueryProduct; import io.flutter.plugins.inapppurchase.Messages.PlatformRecurrenceMode; +import io.flutter.plugins.inapppurchase.Messages.PlatformReplacementMode; import io.flutter.plugins.inapppurchase.Messages.PlatformSubscriptionOfferDetails; import io.flutter.plugins.inapppurchase.Messages.PlatformUserChoiceDetails; import io.flutter.plugins.inapppurchase.Messages.PlatformUserChoiceProduct; @@ -146,6 +152,8 @@ static PlatformProductType toPlatformProductType(@NonNull String typeString) { .setOfferTags(subscriptionOfferDetails.getOfferTags()) .setOfferToken(subscriptionOfferDetails.getOfferToken()) .setPricingPhases(fromPricingPhases(subscriptionOfferDetails.getPricingPhases())) + .setInstallmentPlanDetails( + fromInstallmentPlanDetails(subscriptionOfferDetails.getInstallmentPlanDetails())) .build(); } @@ -170,6 +178,20 @@ static PlatformProductType toPlatformProductType(@NonNull String typeString) { .build(); } + static @Nullable Messages.PlatformInstallmentPlanDetails fromInstallmentPlanDetails( + @Nullable ProductDetails.InstallmentPlanDetails installmentPlanDetails) { + if (installmentPlanDetails == null) { + return null; + } + + return new Messages.PlatformInstallmentPlanDetails.Builder() + .setCommitmentPaymentsCount( + (long) installmentPlanDetails.getInstallmentPlanCommitmentPaymentsCount()) + .setSubsequentCommitmentPaymentsCount( + (long) installmentPlanDetails.getSubsequentInstallmentPlanCommitmentPaymentsCount()) + .build(); + } + static PlatformRecurrenceMode toPlatformRecurrenceMode(int mode) { switch (mode) { case ProductDetails.RecurrenceMode.FINITE_RECURRING: @@ -217,9 +239,27 @@ static PlatformPurchaseState toPlatformPurchaseState(int state) { .setObfuscatedProfileId(accountIdentifiers.getObfuscatedProfileId()) .build()); } + + Purchase.PendingPurchaseUpdate pendingPurchaseUpdate = purchase.getPendingPurchaseUpdate(); + if (pendingPurchaseUpdate != null) { + builder.setPendingPurchaseUpdate(fromPendingPurchaseUpdate(pendingPurchaseUpdate)); + } + return builder.build(); } + static @Nullable PlatformPendingPurchaseUpdate fromPendingPurchaseUpdate( + @Nullable Purchase.PendingPurchaseUpdate pendingPurchaseUpdate) { + if (pendingPurchaseUpdate == null) { + return null; + } + + return new Messages.PlatformPendingPurchaseUpdate.Builder() + .setPurchaseToken(pendingPurchaseUpdate.getPurchaseToken()) + .setProducts(pendingPurchaseUpdate.getProducts()) + .build(); + } + static @NonNull PlatformPurchaseHistoryRecord fromPurchaseHistoryRecord( @NonNull PurchaseHistoryRecord purchaseHistoryRecord) { return new PlatformPurchaseHistoryRecord.Builder() @@ -260,11 +300,41 @@ static PlatformPurchaseState toPlatformPurchaseState(int state) { static @NonNull PlatformBillingResult fromBillingResult(@NonNull BillingResult billingResult) { return new PlatformBillingResult.Builder() - .setResponseCode((long) billingResult.getResponseCode()) + .setResponseCode(fromBillingResponseCode(billingResult.getResponseCode())) .setDebugMessage(billingResult.getDebugMessage()) .build(); } + static @NonNull PlatformBillingResponse fromBillingResponseCode(int billingResponseCode) { + switch (billingResponseCode) { + case BillingClient.BillingResponseCode.FEATURE_NOT_SUPPORTED: + return PlatformBillingResponse.FEATURE_NOT_SUPPORTED; + case BillingClient.BillingResponseCode.SERVICE_DISCONNECTED: + return PlatformBillingResponse.SERVICE_DISCONNECTED; + case BillingClient.BillingResponseCode.OK: + return PlatformBillingResponse.OK; + case BillingClient.BillingResponseCode.USER_CANCELED: + return PlatformBillingResponse.USER_CANCELED; + case BillingClient.BillingResponseCode.SERVICE_UNAVAILABLE: + return PlatformBillingResponse.SERVICE_UNAVAILABLE; + case BillingClient.BillingResponseCode.BILLING_UNAVAILABLE: + return PlatformBillingResponse.BILLING_UNAVAILABLE; + case BillingClient.BillingResponseCode.ITEM_UNAVAILABLE: + return PlatformBillingResponse.ITEM_UNAVAILABLE; + case BillingClient.BillingResponseCode.DEVELOPER_ERROR: + return PlatformBillingResponse.DEVELOPER_ERROR; + case BillingClient.BillingResponseCode.ERROR: + return PlatformBillingResponse.ERROR; + case BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED: + return PlatformBillingResponse.ITEM_ALREADY_OWNED; + case BillingClient.BillingResponseCode.ITEM_NOT_OWNED: + return PlatformBillingResponse.ITEM_NOT_OWNED; + case BillingClient.BillingResponseCode.NETWORK_ERROR: + return PlatformBillingResponse.NETWORK_ERROR; + } + return PlatformBillingResponse.ERROR; + } + static @NonNull PlatformUserChoiceDetails fromUserChoiceDetails( @NonNull UserChoiceDetails userChoiceDetails) { return new PlatformUserChoiceDetails.Builder() @@ -317,6 +387,57 @@ static PlatformPurchaseState toPlatformPurchaseState(int state) { .build(); } + static @NonNull PendingPurchasesParams toPendingPurchasesParams( + @Nullable Messages.PlatformPendingPurchasesParams platformPendingPurchasesParams) { + PendingPurchasesParams.Builder pendingPurchasesBuilder = + PendingPurchasesParams.newBuilder().enableOneTimeProducts(); + if (platformPendingPurchasesParams != null + && platformPendingPurchasesParams.getEnablePrepaidPlans()) { + pendingPurchasesBuilder.enablePrepaidPlans(); + } + return pendingPurchasesBuilder.build(); + } + + static @NonNull String toBillingClientFeature(@NonNull PlatformBillingClientFeature feature) { + switch (feature) { + case ALTERNATIVE_BILLING_ONLY: + return BillingClient.FeatureType.ALTERNATIVE_BILLING_ONLY; + case BILLING_CONFIG: + return BillingClient.FeatureType.BILLING_CONFIG; + case EXTERNAL_OFFER: + return BillingClient.FeatureType.EXTERNAL_OFFER; + case IN_APP_MESSAGING: + return BillingClient.FeatureType.IN_APP_MESSAGING; + case PRICE_CHANGE_CONFIRMATION: + return BillingClient.FeatureType.PRICE_CHANGE_CONFIRMATION; + case PRODUCT_DETAILS: + return BillingClient.FeatureType.PRODUCT_DETAILS; + case SUBSCRIPTIONS: + return BillingClient.FeatureType.SUBSCRIPTIONS; + case SUBSCRIPTIONS_UPDATE: + return BillingClient.FeatureType.SUBSCRIPTIONS_UPDATE; + } + throw new FlutterError("UNKNOWN_FEATURE", "Unknown client feature: " + feature, null); + } + + static int toReplacementMode(@NonNull PlatformReplacementMode replacementMode) { + switch (replacementMode) { + case CHARGE_FULL_PRICE: + return BillingFlowParams.SubscriptionUpdateParams.ReplacementMode.CHARGE_FULL_PRICE; + case CHARGE_PRORATED_PRICE: + return BillingFlowParams.SubscriptionUpdateParams.ReplacementMode.CHARGE_PRORATED_PRICE; + case DEFERRED: + return BillingFlowParams.SubscriptionUpdateParams.ReplacementMode.DEFERRED; + case WITHOUT_PRORATION: + return BillingFlowParams.SubscriptionUpdateParams.ReplacementMode.WITHOUT_PRORATION; + case WITH_TIME_PRORATION: + return BillingFlowParams.SubscriptionUpdateParams.ReplacementMode.WITH_TIME_PRORATION; + case UNKNOWN_REPLACEMENT_MODE: + return BillingFlowParams.SubscriptionUpdateParams.ReplacementMode.UNKNOWN_REPLACEMENT_MODE; + } + return BillingFlowParams.SubscriptionUpdateParams.ReplacementMode.UNKNOWN_REPLACEMENT_MODE; + } + /** * Gets the symbol of for the given currency code for the default {@link Locale.Category#DISPLAY * DISPLAY} locale. For example, for the US Dollar, the symbol is "$" if the default locale is the diff --git a/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/BillingClientFactoryImplTest.java b/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/BillingClientFactoryImplTest.java index d1c91fb8219..bc08ff67d28 100644 --- a/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/BillingClientFactoryImplTest.java +++ b/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/BillingClientFactoryImplTest.java @@ -52,19 +52,19 @@ public void setUp() { @Test public void playBillingOnly() { - // No logic to verify just ensure creation works. + // No logic to verify, just ensure creation works. BillingClient client = factory.createBillingClient( - context, mockCallbackApi, PlatformBillingChoiceMode.PLAY_BILLING_ONLY); + context, mockCallbackApi, PlatformBillingChoiceMode.PLAY_BILLING_ONLY, null); assertNotNull(client); } @Test public void alternativeBillingOnly() { - // No logic to verify just ensure creation works. + // No logic to verify, just ensure creation works. BillingClient client = factory.createBillingClient( - context, mockCallbackApi, PlatformBillingChoiceMode.ALTERNATIVE_BILLING_ONLY); + context, mockCallbackApi, PlatformBillingChoiceMode.ALTERNATIVE_BILLING_ONLY, null); assertNotNull(client); } @@ -78,7 +78,7 @@ public void userChoiceBilling() { final BillingClient billingClient = factory.createBillingClient( - context, mockCallbackApi, PlatformBillingChoiceMode.USER_CHOICE_BILLING); + context, mockCallbackApi, PlatformBillingChoiceMode.USER_CHOICE_BILLING, null); UserChoiceDetails details = mock(UserChoiceDetails.class); final String externalTransactionToken = "someLongTokenId1234"; @@ -98,6 +98,17 @@ public void userChoiceBilling() { assertTrue(callbackCaptor.getValue().getProducts().isEmpty()); } + @Test + public void pendingPurchasesForPrepaidPlans() { + // No logic to verify, just ensure creation works. + Messages.PlatformPendingPurchasesParams params = + new Messages.PlatformPendingPurchasesParams.Builder().setEnablePrepaidPlans(true).build(); + BillingClient client = + factory.createBillingClient( + context, mockCallbackApi, PlatformBillingChoiceMode.PLAY_BILLING_ONLY, params); + assertNotNull(client); + } + @After public void tearDown() throws Exception { openMocks.close(); diff --git a/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/MethodCallHandlerTest.java b/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/MethodCallHandlerTest.java index 58b1c42a0e4..33e6a0fa827 100644 --- a/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/MethodCallHandlerTest.java +++ b/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/MethodCallHandlerTest.java @@ -5,8 +5,8 @@ package io.flutter.plugins.inapppurchase; import static io.flutter.plugins.inapppurchase.MethodCallHandlerImpl.ACTIVITY_UNAVAILABLE; -import static io.flutter.plugins.inapppurchase.MethodCallHandlerImpl.PRORATION_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY; import static io.flutter.plugins.inapppurchase.MethodCallHandlerImpl.REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY; +import static io.flutter.plugins.inapppurchase.Translator.fromBillingResponseCode; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; import static java.util.Collections.unmodifiableList; @@ -59,6 +59,7 @@ import io.flutter.plugins.inapppurchase.Messages.InAppPurchaseCallbackApi; import io.flutter.plugins.inapppurchase.Messages.PlatformAlternativeBillingOnlyReportingDetailsResponse; import io.flutter.plugins.inapppurchase.Messages.PlatformBillingChoiceMode; +import io.flutter.plugins.inapppurchase.Messages.PlatformBillingClientFeature; import io.flutter.plugins.inapppurchase.Messages.PlatformBillingConfigResponse; import io.flutter.plugins.inapppurchase.Messages.PlatformBillingFlowParams; import io.flutter.plugins.inapppurchase.Messages.PlatformBillingResult; @@ -67,6 +68,7 @@ import io.flutter.plugins.inapppurchase.Messages.PlatformPurchaseHistoryResponse; import io.flutter.plugins.inapppurchase.Messages.PlatformPurchasesResponse; import io.flutter.plugins.inapppurchase.Messages.PlatformQueryProduct; +import io.flutter.plugins.inapppurchase.Messages.PlatformReplacementMode; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; @@ -104,22 +106,33 @@ public class MethodCallHandlerTest { @Mock Context context; @Mock ActivityPluginBinding mockActivityPluginBinding; + private final Messages.PlatformPendingPurchasesParams defaultPendingPurchasesParams = + new Messages.PlatformPendingPurchasesParams.Builder().setEnablePrepaidPlans(false).build(); + private final Long DEFAULT_HANDLE = 1L; @Before public void setUp() { openMocks = MockitoAnnotations.openMocks(this); + // Use the same client no matter if alternative billing is enabled or not. when(factory.createBillingClient( - context, mockCallbackApi, PlatformBillingChoiceMode.PLAY_BILLING_ONLY)) + context, + mockCallbackApi, + PlatformBillingChoiceMode.PLAY_BILLING_ONLY, + defaultPendingPurchasesParams)) .thenReturn(mockBillingClient); when(factory.createBillingClient( - context, mockCallbackApi, PlatformBillingChoiceMode.ALTERNATIVE_BILLING_ONLY)) + context, + mockCallbackApi, + PlatformBillingChoiceMode.ALTERNATIVE_BILLING_ONLY, + defaultPendingPurchasesParams)) .thenReturn(mockBillingClient); when(factory.createBillingClient( any(Context.class), any(InAppPurchaseCallbackApi.class), - eq(PlatformBillingChoiceMode.USER_CHOICE_BILLING))) + eq(PlatformBillingChoiceMode.USER_CHOICE_BILLING), + any(Messages.PlatformPendingPurchasesParams.class))) .thenReturn(mockBillingClient); methodChannelHandler = new MethodCallHandlerImpl(activity, context, mockCallbackApi, factory); when(mockActivityPluginBinding.getActivity()).thenReturn(activity); @@ -159,10 +172,15 @@ public void isReady_clientDisconnected() { @Test public void startConnection() { ArgumentCaptor captor = - mockStartConnection(PlatformBillingChoiceMode.PLAY_BILLING_ONLY); + mockStartConnection( + PlatformBillingChoiceMode.PLAY_BILLING_ONLY, defaultPendingPurchasesParams); verify(platformBillingResult, never()).success(any()); verify(factory, times(1)) - .createBillingClient(context, mockCallbackApi, PlatformBillingChoiceMode.PLAY_BILLING_ONLY); + .createBillingClient( + context, + mockCallbackApi, + PlatformBillingChoiceMode.PLAY_BILLING_ONLY, + defaultPendingPurchasesParams); BillingResult billingResult = buildBillingResult(); captor.getValue().onBillingSetupFinished(billingResult); @@ -177,11 +195,15 @@ public void startConnection() { @Test public void startConnectionAlternativeBillingOnly() { ArgumentCaptor captor = - mockStartConnection(PlatformBillingChoiceMode.ALTERNATIVE_BILLING_ONLY); + mockStartConnection( + PlatformBillingChoiceMode.ALTERNATIVE_BILLING_ONLY, defaultPendingPurchasesParams); verify(platformBillingResult, never()).success(any()); verify(factory, times(1)) .createBillingClient( - context, mockCallbackApi, PlatformBillingChoiceMode.ALTERNATIVE_BILLING_ONLY); + context, + mockCallbackApi, + PlatformBillingChoiceMode.ALTERNATIVE_BILLING_ONLY, + defaultPendingPurchasesParams); BillingResult billingResult = buildBillingResult(); captor.getValue().onBillingSetupFinished(billingResult); @@ -193,17 +215,41 @@ public void startConnectionAlternativeBillingOnly() { verify(platformBillingResult, never()).error(any()); } + @Test + public void startConnectionPendingPurchasesPrepaidPlans() { + Messages.PlatformPendingPurchasesParams pendingPurchasesParams = + new Messages.PlatformPendingPurchasesParams.Builder().setEnablePrepaidPlans(true).build(); + ArgumentCaptor captor = + mockStartConnection(PlatformBillingChoiceMode.USER_CHOICE_BILLING, pendingPurchasesParams); + verify(platformBillingResult, never()).success(any()); + verify(factory, times(1)) + .createBillingClient( + context, + mockCallbackApi, + PlatformBillingChoiceMode.USER_CHOICE_BILLING, + pendingPurchasesParams); + + BillingResult billingResult = buildBillingResult(); + captor.getValue().onBillingSetupFinished(billingResult); + + ArgumentCaptor resultCaptor = + ArgumentCaptor.forClass(PlatformBillingResult.class); + verify(platformBillingResult, times(1)).success(resultCaptor.capture()); + } + @Test public void startConnectionUserChoiceBilling() { ArgumentCaptor captor = - mockStartConnection(PlatformBillingChoiceMode.USER_CHOICE_BILLING); + mockStartConnection( + PlatformBillingChoiceMode.USER_CHOICE_BILLING, defaultPendingPurchasesParams); verify(platformBillingResult, never()).success(any()); verify(factory, times(1)) .createBillingClient( any(Context.class), any(InAppPurchaseCallbackApi.class), - eq(PlatformBillingChoiceMode.USER_CHOICE_BILLING)); + eq(PlatformBillingChoiceMode.USER_CHOICE_BILLING), + any(Messages.PlatformPendingPurchasesParams.class)); BillingResult billingResult = BillingResult.newBuilder() @@ -221,10 +267,15 @@ public void startConnectionUserChoiceBilling() { public void userChoiceBillingOnSecondConnection() { // First connection. ArgumentCaptor captor1 = - mockStartConnection(PlatformBillingChoiceMode.PLAY_BILLING_ONLY); + mockStartConnection( + PlatformBillingChoiceMode.PLAY_BILLING_ONLY, defaultPendingPurchasesParams); verify(platformBillingResult, never()).success(any()); verify(factory, times(1)) - .createBillingClient(context, mockCallbackApi, PlatformBillingChoiceMode.PLAY_BILLING_ONLY); + .createBillingClient( + context, + mockCallbackApi, + PlatformBillingChoiceMode.PLAY_BILLING_ONLY, + defaultPendingPurchasesParams); BillingResult billingResult1 = BillingResult.newBuilder() @@ -248,13 +299,15 @@ public void userChoiceBillingOnSecondConnection() { // Second connection. ArgumentCaptor captor2 = - mockStartConnection(PlatformBillingChoiceMode.USER_CHOICE_BILLING); + mockStartConnection( + PlatformBillingChoiceMode.USER_CHOICE_BILLING, defaultPendingPurchasesParams); verify(platformBillingResult, never()).success(any()); verify(factory, times(1)) .createBillingClient( any(Context.class), any(InAppPurchaseCallbackApi.class), - eq(PlatformBillingChoiceMode.USER_CHOICE_BILLING)); + eq(PlatformBillingChoiceMode.USER_CHOICE_BILLING), + eq(defaultPendingPurchasesParams)); BillingResult billingResult2 = BillingResult.newBuilder() @@ -273,7 +326,10 @@ public void startConnection_multipleCalls() { doNothing().when(mockBillingClient).startConnection(captor.capture()); methodChannelHandler.startConnection( - DEFAULT_HANDLE, PlatformBillingChoiceMode.PLAY_BILLING_ONLY, platformBillingResult); + DEFAULT_HANDLE, + PlatformBillingChoiceMode.PLAY_BILLING_ONLY, + defaultPendingPurchasesParams, + platformBillingResult); verify(platformBillingResult, never()).success(any()); BillingResult billingResult1 = buildBillingResult(); BillingResult billingResult2 = @@ -295,7 +351,8 @@ public void startConnection_multipleCalls() { ArgumentCaptor.forClass(PlatformBillingResult.class); verify(platformBillingResult, times(1)).success(resultCaptor.capture()); assertEquals( - resultCaptor.getValue().getResponseCode().longValue(), billingResult1.getResponseCode()); + resultCaptor.getValue().getResponseCode(), + fromBillingResponseCode(billingResult1.getResponseCode())); assertEquals(resultCaptor.getValue().getDebugMessage(), billingResult1.getDebugMessage()); verify(platformBillingResult, never()).error(any()); } @@ -480,7 +537,10 @@ public void endConnection() { @SuppressWarnings("unchecked") final Messages.Result mockResult = mock(Messages.Result.class); methodChannelHandler.startConnection( - disconnectCallbackHandle, PlatformBillingChoiceMode.PLAY_BILLING_ONLY, mockResult); + disconnectCallbackHandle, + PlatformBillingChoiceMode.PLAY_BILLING_ONLY, + defaultPendingPurchasesParams, + mockResult); final BillingClientStateListener stateListener = captor.getValue(); // Disconnect the connected client @@ -555,10 +615,8 @@ public void launchBillingFlow_null_AccountId_do_not_crash() { queryForProducts(singletonList(productId)); PlatformBillingFlowParams.Builder paramsBuilder = new PlatformBillingFlowParams.Builder(); paramsBuilder.setProduct(productId); - paramsBuilder.setProrationMode( - (long) PRORATION_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); paramsBuilder.setReplacementMode( - (long) REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); + REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); // Launch the billing flow BillingResult billingResult = buildBillingResult(); @@ -582,10 +640,8 @@ public void launchBillingFlow_ok_null_OldProduct() { PlatformBillingFlowParams.Builder paramsBuilder = new PlatformBillingFlowParams.Builder(); paramsBuilder.setProduct(productId); paramsBuilder.setAccountId(accountId); - paramsBuilder.setProrationMode( - (long) PRORATION_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); paramsBuilder.setReplacementMode( - (long) REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); + REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); // Launch the billing flow BillingResult billingResult = buildBillingResult(); @@ -613,10 +669,8 @@ public void launchBillingFlow_ok_null_Activity() { PlatformBillingFlowParams.Builder paramsBuilder = new PlatformBillingFlowParams.Builder(); paramsBuilder.setProduct(productId); paramsBuilder.setAccountId(accountId); - paramsBuilder.setProrationMode( - (long) PRORATION_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); paramsBuilder.setReplacementMode( - (long) REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); + REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); // Assert that the synchronous call throws an exception. FlutterError exception = @@ -638,10 +692,8 @@ public void launchBillingFlow_ok_oldProduct() { paramsBuilder.setProduct(productId); paramsBuilder.setAccountId(accountId); paramsBuilder.setOldProduct(oldProductId); - paramsBuilder.setProrationMode( - (long) PRORATION_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); paramsBuilder.setReplacementMode( - (long) REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); + REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); // Launch the billing flow BillingResult billingResult = buildBillingResult(); @@ -667,10 +719,8 @@ public void launchBillingFlow_ok_AccountId() { PlatformBillingFlowParams.Builder paramsBuilder = new PlatformBillingFlowParams.Builder(); paramsBuilder.setProduct(productId); paramsBuilder.setAccountId(accountId); - paramsBuilder.setProrationMode( - (long) PRORATION_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); paramsBuilder.setReplacementMode( - (long) REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); + REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); // Launch the billing flow BillingResult billingResult = buildBillingResult(); @@ -687,27 +737,21 @@ public void launchBillingFlow_ok_AccountId() { assertResultsMatch(platformResult, billingResult); } - // TODO(gmackall): Replace uses of deprecated ProrationMode enum values with new - // ReplacementMode enum values. - // https://github.com/flutter/flutter/issues/128957. @Test - @SuppressWarnings(value = "deprecation") public void launchBillingFlow_ok_Proration() { // Fetch the product details first and query the method call String productId = "foo"; String oldProductId = "oldFoo"; String purchaseToken = "purchaseTokenFoo"; String accountId = "account"; - int prorationMode = BillingFlowParams.ProrationMode.IMMEDIATE_AND_CHARGE_PRORATED_PRICE; + PlatformReplacementMode replacementMode = PlatformReplacementMode.CHARGE_PRORATED_PRICE; queryForProducts(unmodifiableList(asList(productId, oldProductId))); PlatformBillingFlowParams.Builder paramsBuilder = new PlatformBillingFlowParams.Builder(); paramsBuilder.setProduct(productId); paramsBuilder.setAccountId(accountId); paramsBuilder.setOldProduct(oldProductId); paramsBuilder.setPurchaseToken(purchaseToken); - paramsBuilder.setProrationMode((long) prorationMode); - paramsBuilder.setReplacementMode( - (long) REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); + paramsBuilder.setReplacementMode(replacementMode); // Launch the billing flow BillingResult billingResult = buildBillingResult(); @@ -724,25 +768,19 @@ public void launchBillingFlow_ok_Proration() { assertResultsMatch(platformResult, billingResult); } - // TODO(gmackall): Replace uses of deprecated ProrationMode enum values with new - // ReplacementMode enum values. - // https://github.com/flutter/flutter/issues/128957. @Test - @SuppressWarnings(value = "deprecation") public void launchBillingFlow_ok_Proration_with_null_OldProduct() { // Fetch the product details first and query the method call String productId = "foo"; String accountId = "account"; String queryOldProductId = "oldFoo"; - int prorationMode = BillingFlowParams.ProrationMode.IMMEDIATE_AND_CHARGE_PRORATED_PRICE; + PlatformReplacementMode replacementMode = PlatformReplacementMode.CHARGE_PRORATED_PRICE; queryForProducts(unmodifiableList(asList(productId, queryOldProductId))); PlatformBillingFlowParams.Builder paramsBuilder = new PlatformBillingFlowParams.Builder(); paramsBuilder.setProduct(productId); paramsBuilder.setAccountId(accountId); paramsBuilder.setOldProduct(null); - paramsBuilder.setProrationMode((long) prorationMode); - paramsBuilder.setReplacementMode( - (long) REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); + paramsBuilder.setReplacementMode(replacementMode); // Launch the billing flow BillingResult billingResult = buildBillingResult(); @@ -766,16 +804,13 @@ public void launchBillingFlow_ok_Replacement_with_null_OldProduct() { String productId = "foo"; String accountId = "account"; String queryOldProductId = "oldFoo"; - int replacementMode = - BillingFlowParams.SubscriptionUpdateParams.ReplacementMode.CHARGE_PRORATED_PRICE; + PlatformReplacementMode replacementMode = PlatformReplacementMode.CHARGE_PRORATED_PRICE; queryForProducts(unmodifiableList(asList(productId, queryOldProductId))); PlatformBillingFlowParams.Builder paramsBuilder = new PlatformBillingFlowParams.Builder(); paramsBuilder.setProduct(productId); paramsBuilder.setAccountId(accountId); paramsBuilder.setOldProduct(null); - paramsBuilder.setProrationMode( - (long) PRORATION_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); - paramsBuilder.setReplacementMode((long) replacementMode); + paramsBuilder.setReplacementMode(replacementMode); // Launch the billing flow BillingResult billingResult = buildBillingResult(); @@ -793,60 +828,20 @@ public void launchBillingFlow_ok_Replacement_with_null_OldProduct() { } @Test - @SuppressWarnings(value = "deprecation") - public void launchBillingFlow_ok_Proration_and_Replacement_conflict() { - // Fetch the product details first and query the method call - String productId = "foo"; - String accountId = "account"; - String queryOldProductId = "oldFoo"; - int prorationMode = BillingFlowParams.ProrationMode.IMMEDIATE_AND_CHARGE_PRORATED_PRICE; - int replacementMode = - BillingFlowParams.SubscriptionUpdateParams.ReplacementMode.CHARGE_PRORATED_PRICE; - queryForProducts(unmodifiableList(asList(productId, queryOldProductId))); - PlatformBillingFlowParams.Builder paramsBuilder = new PlatformBillingFlowParams.Builder(); - paramsBuilder.setProduct(productId); - paramsBuilder.setAccountId(accountId); - paramsBuilder.setOldProduct(queryOldProductId); - paramsBuilder.setProrationMode((long) prorationMode); - paramsBuilder.setReplacementMode((long) replacementMode); - - // Launch the billing flow - BillingResult billingResult = buildBillingResult(); - when(mockBillingClient.launchBillingFlow(any(), any())).thenReturn(billingResult); - - // Assert that the synchronous call throws an exception. - FlutterError exception = - assertThrows( - FlutterError.class, - () -> methodChannelHandler.launchBillingFlow(paramsBuilder.build())); - assertEquals("IN_APP_PURCHASE_CONFLICT_PRORATION_MODE_REPLACEMENT_MODE", exception.code); - assertTrue( - Objects.requireNonNull(exception.getMessage()) - .contains( - "launchBillingFlow failed because you provided both prorationMode and replacementMode. You can only provide one of them.")); - } - - // TODO(gmackall): Replace uses of deprecated ProrationMode enum values with new - // ReplacementMode enum values. - // https://github.com/flutter/flutter/issues/128957. - @Test - @SuppressWarnings(value = "deprecation") public void launchBillingFlow_ok_Full() { // Fetch the product details first and query the method call String productId = "foo"; String oldProductId = "oldFoo"; String purchaseToken = "purchaseTokenFoo"; String accountId = "account"; - int prorationMode = BillingFlowParams.ProrationMode.IMMEDIATE_AND_CHARGE_FULL_PRICE; + PlatformReplacementMode replacementMode = PlatformReplacementMode.CHARGE_FULL_PRICE; queryForProducts(unmodifiableList(asList(productId, oldProductId))); PlatformBillingFlowParams.Builder paramsBuilder = new PlatformBillingFlowParams.Builder(); paramsBuilder.setProduct(productId); paramsBuilder.setAccountId(accountId); paramsBuilder.setOldProduct(oldProductId); paramsBuilder.setPurchaseToken(purchaseToken); - paramsBuilder.setProrationMode((long) prorationMode); - paramsBuilder.setReplacementMode( - (long) REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); + paramsBuilder.setReplacementMode(replacementMode); // Launch the billing flow BillingResult billingResult = buildBillingResult(); @@ -872,10 +867,8 @@ public void launchBillingFlow_clientDisconnected() { PlatformBillingFlowParams.Builder paramsBuilder = new PlatformBillingFlowParams.Builder(); paramsBuilder.setProduct(productId); paramsBuilder.setAccountId(accountId); - paramsBuilder.setProrationMode( - (long) PRORATION_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); paramsBuilder.setReplacementMode( - (long) REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); + REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); // Assert that the synchronous call throws an exception. FlutterError exception = @@ -895,10 +888,8 @@ public void launchBillingFlow_productNotFound() { PlatformBillingFlowParams.Builder paramsBuilder = new PlatformBillingFlowParams.Builder(); paramsBuilder.setProduct(productId); paramsBuilder.setAccountId(accountId); - paramsBuilder.setProrationMode( - (long) PRORATION_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); paramsBuilder.setReplacementMode( - (long) REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); + REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); // Assert that the synchronous call throws an exception. FlutterError exception = @@ -921,10 +912,8 @@ public void launchBillingFlow_oldProductNotFound() { paramsBuilder.setProduct(productId); paramsBuilder.setAccountId(accountId); paramsBuilder.setOldProduct(oldProductId); - paramsBuilder.setProrationMode( - (long) PRORATION_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); paramsBuilder.setReplacementMode( - (long) REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); + REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); // Assert that the synchronous call throws an exception. FlutterError exception = @@ -982,12 +971,13 @@ public void queryPurchases_returns_success() { PlatformPurchasesResponse purchasesResponse = resultCaptor.getValue(); assertEquals( - purchasesResponse.getBillingResult().getResponseCode().longValue(), - BillingClient.BillingResponseCode.OK); + purchasesResponse.getBillingResult().getResponseCode(), + Messages.PlatformBillingResponse.OK); assertTrue(purchasesResponse.getPurchases().isEmpty()); } @Test + @SuppressWarnings(value = "deprecation") public void queryPurchaseHistoryAsync() { // Set up an established billing client and all our mocked responses establishConnectedBillingClient(); @@ -1015,6 +1005,7 @@ public void queryPurchaseHistoryAsync() { } @Test + @SuppressWarnings(value = "deprecation") public void queryPurchaseHistoryAsync_clientDisconnected() { methodChannelHandler.endConnection(); @@ -1116,7 +1107,7 @@ public void isFutureSupported_true() { BillingResult billingResult = buildBillingResult(BillingClient.BillingResponseCode.OK); when(mockBillingClient.isFeatureSupported(feature)).thenReturn(billingResult); - assertTrue(methodChannelHandler.isFeatureSupported(feature)); + assertTrue(methodChannelHandler.isFeatureSupported(PlatformBillingClientFeature.SUBSCRIPTIONS)); } @Test @@ -1127,30 +1118,34 @@ public void isFutureSupported_false() { BillingResult billingResult = buildBillingResult(BillingResponseCode.FEATURE_NOT_SUPPORTED); when(mockBillingClient.isFeatureSupported(feature)).thenReturn(billingResult); - assertFalse(methodChannelHandler.isFeatureSupported(feature)); + assertFalse( + methodChannelHandler.isFeatureSupported(PlatformBillingClientFeature.SUBSCRIPTIONS)); } /** * Call {@link MethodCallHandlerImpl#startConnection(Long, PlatformBillingChoiceMode, - * Messages.Result)} with startup params. + * Messages.PlatformPendingPurchasesParams, Messages.Result)} with startup params. * *

Defaults to play billing only which is the default. */ private ArgumentCaptor mockStartConnection() { - return mockStartConnection(PlatformBillingChoiceMode.PLAY_BILLING_ONLY); + return mockStartConnection( + PlatformBillingChoiceMode.PLAY_BILLING_ONLY, defaultPendingPurchasesParams); } /** * Call {@link MethodCallHandlerImpl#startConnection(Long, PlatformBillingChoiceMode, - * Messages.Result)} with startup params. + * Messages.PlatformPendingPurchasesParams, Messages.Result)} with startup params. */ private ArgumentCaptor mockStartConnection( - PlatformBillingChoiceMode billingChoiceMode) { + PlatformBillingChoiceMode billingChoiceMode, + Messages.PlatformPendingPurchasesParams pendingPurchasesParams) { ArgumentCaptor captor = ArgumentCaptor.forClass(BillingClientStateListener.class); doNothing().when(mockBillingClient).startConnection(captor.capture()); - methodChannelHandler.startConnection(DEFAULT_HANDLE, billingChoiceMode, platformBillingResult); + methodChannelHandler.startConnection( + DEFAULT_HANDLE, billingChoiceMode, pendingPurchasesParams, platformBillingResult); return captor; } @@ -1158,7 +1153,10 @@ private void establishConnectedBillingClient() { @SuppressWarnings("unchecked") final Messages.Result mockResult = mock(Messages.Result.class); methodChannelHandler.startConnection( - DEFAULT_HANDLE, PlatformBillingChoiceMode.PLAY_BILLING_ONLY, mockResult); + DEFAULT_HANDLE, + PlatformBillingChoiceMode.PLAY_BILLING_ONLY, + defaultPendingPurchasesParams, + mockResult); } private void queryForProducts(List productIdList) { @@ -1255,7 +1253,8 @@ private BillingResult buildBillingResult(int responseCode) { } private void assertResultsMatch(PlatformBillingResult pigeonResult, BillingResult nativeResult) { - assertEquals(pigeonResult.getResponseCode().longValue(), nativeResult.getResponseCode()); + assertEquals( + pigeonResult.getResponseCode(), fromBillingResponseCode(nativeResult.getResponseCode())); assertEquals(pigeonResult.getDebugMessage(), nativeResult.getDebugMessage()); } diff --git a/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/TranslatorTest.java b/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/TranslatorTest.java index fff2ef2ffbc..a3235f923c8 100644 --- a/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/TranslatorTest.java +++ b/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/TranslatorTest.java @@ -165,7 +165,7 @@ public void fromBillingResult() { .build(); Messages.PlatformBillingResult platformResult = Translator.fromBillingResult(newBillingResult); - assertEquals(platformResult.getResponseCode().longValue(), newBillingResult.getResponseCode()); + assertEquals(Messages.PlatformBillingResponse.OK, platformResult.getResponseCode()); assertEquals(platformResult.getDebugMessage(), newBillingResult.getDebugMessage()); } @@ -175,7 +175,7 @@ public void fromBillingResult_debugMessageNull() { BillingResult.newBuilder().setResponseCode(BillingClient.BillingResponseCode.OK).build(); Messages.PlatformBillingResult platformResult = Translator.fromBillingResult(newBillingResult); - assertEquals(platformResult.getResponseCode().longValue(), newBillingResult.getResponseCode()); + assertEquals(Messages.PlatformBillingResponse.OK, platformResult.getResponseCode()); assertEquals(platformResult.getDebugMessage(), newBillingResult.getDebugMessage()); } @@ -242,6 +242,15 @@ private void assertSerialized( assertEquals(expected.getOfferTags(), serialized.getOfferTags()); assertEquals(expected.getOfferToken(), serialized.getOfferToken()); assertSerialized(expected.getPricingPhases(), serialized.getPricingPhases()); + + ProductDetails.InstallmentPlanDetails expectedInstallmentPlanDetails = + expected.getInstallmentPlanDetails(); + Messages.PlatformInstallmentPlanDetails serializedInstallmentPlanDetails = + serialized.getInstallmentPlanDetails(); + assertEquals(expectedInstallmentPlanDetails == null, serializedInstallmentPlanDetails == null); + if (expectedInstallmentPlanDetails != null && serializedInstallmentPlanDetails != null) { + assertSerialized(expectedInstallmentPlanDetails, serializedInstallmentPlanDetails); + } } private void assertSerialized( @@ -287,6 +296,17 @@ private void assertSerialized(Purchase expected, Messages.PlatformPurchase seria Objects.requireNonNull(serialized.getAccountIdentifiers()).getObfuscatedProfileId()); } + private void assertSerialized( + ProductDetails.InstallmentPlanDetails expected, + Messages.PlatformInstallmentPlanDetails serialized) { + assertEquals( + expected.getInstallmentPlanCommitmentPaymentsCount(), + serialized.getCommitmentPaymentsCount().intValue()); + assertEquals( + expected.getSubsequentInstallmentPlanCommitmentPaymentsCount(), + serialized.getSubsequentCommitmentPaymentsCount().intValue()); + } + private String productTypeFromPlatform(Messages.PlatformProductType type) { switch (type) { case INAPP: diff --git a/packages/in_app_purchase/in_app_purchase_android/build.yaml b/packages/in_app_purchase/in_app_purchase_android/build.yaml deleted file mode 100644 index 651a557fc1c..00000000000 --- a/packages/in_app_purchase/in_app_purchase_android/build.yaml +++ /dev/null @@ -1,8 +0,0 @@ -# See https://pub.dev/packages/build_config -targets: - $default: - builders: - json_serializable: - options: - any_map: true - create_to_json: false 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 4773446cdee..04e8e23d262 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 @@ -424,8 +424,8 @@ class _MyAppState extends State<_MyApp> { changeSubscriptionParam: oldSubscription != null ? ChangeSubscriptionParam( oldPurchaseDetails: oldSubscription, - prorationMode: ProrationMode - .immediateWithTimeProration) + replacementMode: + ReplacementMode.withTimeProration) : null); if (productDetails.id == _kConsumableId) { _inAppPurchasePlatform.buyConsumable( @@ -680,19 +680,15 @@ class _FeatureCard extends StatelessWidget { } String _featureToString(BillingClientFeature feature) { - switch (feature) { - case BillingClientFeature.inAppItemsOnVR: - return 'inAppItemsOnVR'; - case BillingClientFeature.priceChangeConfirmation: - return 'priceChangeConfirmation'; - case BillingClientFeature.productDetails: - return 'productDetails'; - case BillingClientFeature.subscriptions: - return 'subscriptions'; - case BillingClientFeature.subscriptionsOnVR: - return 'subscriptionsOnVR'; - case BillingClientFeature.subscriptionsUpdate: - return 'subscriptionsUpdate'; - } + return switch (feature) { + BillingClientFeature.alternativeBillingOnly => 'alternativeBillingOnly', + BillingClientFeature.priceChangeConfirmation => 'priceChangeConfirmation', + BillingClientFeature.productDetails => 'productDetails', + BillingClientFeature.subscriptions => 'subscriptions', + BillingClientFeature.subscriptionsUpdate => 'subscriptionsUpdate', + BillingClientFeature.billingConfig => 'billingConfig', + BillingClientFeature.externalOffer => 'externalOffer', + BillingClientFeature.inAppMessaging => 'inAppMessaging', + }; } } diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/alternative_billing_only_reporting_details_wrapper.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/alternative_billing_only_reporting_details_wrapper.dart index bbabd1477a2..50162f62466 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/alternative_billing_only_reporting_details_wrapper.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/alternative_billing_only_reporting_details_wrapper.dart @@ -3,15 +3,9 @@ // found in the LICENSE file. import 'package:flutter/material.dart'; -import 'package:json_annotation/json_annotation.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 -// rebuild and watch for further changes. -part 'alternative_billing_only_reporting_details_wrapper.g.dart'; - /// The error message shown when the map representing details is invalid from method channel. /// /// This usually indicates a serious underlying code issue in the plugin. @@ -20,8 +14,6 @@ const String kInvalidAlternativeBillingReportingDetailsErrorMessage = 'Invalid AlternativeBillingReportingDetails map from method channel.'; /// Params containing the response code and the debug message from the Play Billing API response. -@JsonSerializable() -@BillingResponseConverter() @immutable class AlternativeBillingOnlyReportingDetailsWrapper implements HasBillingResponse { @@ -31,23 +23,6 @@ class AlternativeBillingOnlyReportingDetailsWrapper this.debugMessage, this.externalTransactionToken = ''}); - /// Constructs an instance of this from a key value map of data. - /// - /// The map needs to have named string keys with values matching the names and - /// types of all of the members on this class. - @Deprecated('JSON serialization is not intended for public use, and will ' - 'be removed in a future version.') - factory AlternativeBillingOnlyReportingDetailsWrapper.fromJson( - Map? map) { - if (map == null || map.isEmpty) { - return const AlternativeBillingOnlyReportingDetailsWrapper( - responseCode: BillingResponse.error, - debugMessage: kInvalidAlternativeBillingReportingDetailsErrorMessage, - ); - } - return _$AlternativeBillingOnlyReportingDetailsWrapperFromJson(map); - } - /// Response code returned in the Play Billing API calls. @override final BillingResponse responseCode; @@ -56,11 +31,9 @@ class AlternativeBillingOnlyReportingDetailsWrapper /// /// Defaults to `null`. /// This message uses an en-US locale and should not be shown to users. - @JsonKey(defaultValue: '') final String? debugMessage; /// https://developer.android.com/reference/com/android/billingclient/api/AlternativeBillingOnlyReportingDetails#getExternalTransactionToken() - @JsonKey(defaultValue: '') final String externalTransactionToken; @override diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/alternative_billing_only_reporting_details_wrapper.g.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/alternative_billing_only_reporting_details_wrapper.g.dart deleted file mode 100644 index 04578fb4a45..00000000000 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/alternative_billing_only_reporting_details_wrapper.g.dart +++ /dev/null @@ -1,17 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'alternative_billing_only_reporting_details_wrapper.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -AlternativeBillingOnlyReportingDetailsWrapper - _$AlternativeBillingOnlyReportingDetailsWrapperFromJson(Map json) => - AlternativeBillingOnlyReportingDetailsWrapper( - responseCode: const BillingResponseConverter() - .fromJson((json['responseCode'] as num?)?.toInt()), - debugMessage: json['debugMessage'] as String? ?? '', - externalTransactionToken: - json['externalTransactionToken'] as String? ?? '', - ); 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 8e265dbdcee..0c63080686a 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,6 +8,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'billing_client_wrapper.dart'; +import 'pending_purchases_params_wrapper.dart'; import 'purchase_wrapper.dart'; import 'user_choice_details_wrapper.dart'; @@ -44,6 +45,8 @@ class BillingClientManager { BillingClientManager( {@visibleForTesting BillingClientFactory? billingClientFactory}) : _billingChoiceMode = BillingChoiceMode.playBillingOnly, + _pendingPurchasesParams = + const PendingPurchasesParamsWrapper(enablePrepaidPlans: false), _billingClientFactory = billingClientFactory ?? _createBillingClient { _connect(); } @@ -85,6 +88,7 @@ class BillingClientManager { BillingChoiceMode _billingChoiceMode; final BillingClientFactory _billingClientFactory; + PendingPurchasesParamsWrapper _pendingPurchasesParams; bool _isConnecting = false; bool _isDisposed = false; @@ -161,9 +165,14 @@ class BillingClientManager { Future reconnectWithBillingChoiceMode( BillingChoiceMode billingChoiceMode) async { _billingChoiceMode = billingChoiceMode; - // Ends connection and triggers OnBillingServiceDisconnected, which causes reconnect. - await client.endConnection(); - await _connect(); + await _reconnect(); + } + + /// Ends connection to [BillingClient] and reconnects with [pendingPurchasesParams]. + Future reconnectWithPendingPurchasesParams( + PendingPurchasesParamsWrapper pendingPurchasesParams) async { + _pendingPurchasesParams = pendingPurchasesParams; + await _reconnect(); } // If disposed, does nothing. @@ -179,13 +188,21 @@ class BillingClientManager { _isConnecting = true; _readyFuture = Future.sync(() async { await client.startConnection( - onBillingServiceDisconnected: _connect, - billingChoiceMode: _billingChoiceMode); + onBillingServiceDisconnected: _connect, + billingChoiceMode: _billingChoiceMode, + pendingPurchasesParams: _pendingPurchasesParams, + ); _isConnecting = false; }); return _readyFuture; } + Future _reconnect() async { + // Ends connection and triggers OnBillingServiceDisconnected, which causes reconnect. + await client.endConnection(); + await _connect(); + } + void _onPurchasesUpdated(PurchasesResultWrapper event) { if (_isDisposed) { return; 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 1f46c3f1de2..17dfbf29c19 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 @@ -5,14 +5,12 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; -import 'package:json_annotation/json_annotation.dart'; import '../../billing_client_wrappers.dart'; import '../messages.g.dart'; import '../pigeon_converters.dart'; import 'billing_config_wrapper.dart'; - -part 'billing_client_wrapper.g.dart'; +import 'pending_purchases_params_wrapper.dart'; /// Callback triggered by Play in response to purchase activity. /// @@ -81,18 +79,6 @@ class BillingClient { return _hostApi.isReady(); } - /// Enable the [BillingClientWrapper] to handle pending purchases. - /// - /// **Deprecation warning:** it is no longer required to call - /// [enablePendingPurchases] when initializing your application. - @Deprecated( - 'The requirement to call `enablePendingPurchases()` has become obsolete ' - "since Google Play no longer accepts app submissions that don't support " - 'pending purchases.') - void enablePendingPurchases() { - // No-op, until it is time to completely remove this method from the API. - } - /// Calls /// [`BillingClient#startConnection(BillingClientStateListener)`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.html#startconnection) /// to create and connect a `BillingClient` instance. @@ -103,14 +89,23 @@ class BillingClient { /// /// This triggers the creation of a new `BillingClient` instance in Java if /// one doesn't already exist. - Future startConnection( - {required OnBillingServiceDisconnected onBillingServiceDisconnected, - BillingChoiceMode billingChoiceMode = - BillingChoiceMode.playBillingOnly}) async { + Future startConnection({ + required OnBillingServiceDisconnected onBillingServiceDisconnected, + BillingChoiceMode billingChoiceMode = BillingChoiceMode.playBillingOnly, + PendingPurchasesParamsWrapper? pendingPurchasesParams, + }) async { hostCallbackHandler.disconnectCallbacks.add(onBillingServiceDisconnected); - return resultWrapperFromPlatform(await _hostApi.startConnection( + return resultWrapperFromPlatform( + await _hostApi.startConnection( hostCallbackHandler.disconnectCallbacks.length - 1, - platformBillingChoiceMode(billingChoiceMode))); + platformBillingChoiceMode(billingChoiceMode), + switch (pendingPurchasesParams) { + final PendingPurchasesParamsWrapper params => + pendingPurchasesParamsFromWrapper(params), + null => PlatformPendingPurchasesParams(enablePrepaidPlans: false) + }, + ), + ); } /// Calls @@ -181,7 +176,7 @@ class BillingClient { /// existing subscription. /// The [oldProduct](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.Builder#setOldPurchaseToken(java.lang.String)) and [purchaseToken] are the product id and purchase token that the user is upgrading or downgrading from. /// [purchaseToken] must not be `null` if [oldProduct] is not `null`. - /// The [prorationMode](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.Builder#setReplaceProrationMode(int)) is the mode of proration during subscription upgrade/downgrade. + /// The [replacementMode](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.Builder#setSubscriptionReplacementMode(int)) is the mode of replacement during subscription upgrade/downgrade. /// This value will only be effective if the `oldProduct` is also set. Future launchBillingFlow( {required String product, @@ -190,17 +185,15 @@ class BillingClient { String? obfuscatedProfileId, String? oldProduct, String? purchaseToken, - ProrationMode? prorationMode, ReplacementMode? replacementMode}) async { assert((oldProduct == null) == (purchaseToken == null), 'oldProduct and purchaseToken must both be set, or both be null.'); return resultWrapperFromPlatform( await _hostApi.launchBillingFlow(PlatformBillingFlowParams( product: product, - prorationMode: const ProrationModeConverter().toJson(prorationMode ?? - ProrationMode.unknownSubscriptionUpgradeDowngradePolicy), - replacementMode: const ReplacementModeConverter() - .toJson(replacementMode ?? ReplacementMode.unknownReplacementMode), + replacementMode: replacementModeFromWrapper( + replacementMode ?? ReplacementMode.unknownReplacementMode, + ), offerToken: offerToken, accountId: accountId, obfuscatedProfileId: obfuscatedProfileId, @@ -253,6 +246,7 @@ class BillingClient { /// /// This wraps /// [`BillingClient#queryPurchaseHistoryAsync(QueryPurchaseHistoryParams, PurchaseHistoryResponseListener)`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient#queryPurchaseHistoryAsync(com.android.billingclient.api.QueryPurchaseHistoryParams,%20com.android.billingclient.api.PurchaseHistoryResponseListener)). + @Deprecated('Use queryPurchases') Future queryPurchaseHistory( ProductType productType) async { return purchaseHistoryResultFromPlatform( @@ -298,8 +292,8 @@ class BillingClient { /// 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 _hostApi.isFeatureSupported( - const BillingClientFeatureConverter().toJson(feature)); + return _hostApi + .isFeatureSupported(billingClientFeatureFromWrapper(feature)); } /// Fetches billing config info into a [BillingConfigWrapper] object. @@ -382,62 +376,44 @@ typedef OnBillingServiceDisconnected = void Function(); /// [`BillingClient.BillingResponse`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.BillingResponse). /// See the `BillingResponse` docs for more explanation of the different /// constants. -@JsonEnum(alwaysCreate: true) enum BillingResponse { - // WARNING: Changes to this class need to be reflected in our generated code. - // Run `flutter packages pub run build_runner watch` to rebuild and watch for - // further changes. - /// The request has reached the maximum timeout before Google Play responds. - @JsonValue(-3) serviceTimeout, /// The requested feature is not supported by Play Store on the current device. - @JsonValue(-2) featureNotSupported, /// The Play Store service is not connected now - potentially transient state. - @JsonValue(-1) serviceDisconnected, /// Success. - @JsonValue(0) ok, /// The user pressed back or canceled a dialog. - @JsonValue(1) userCanceled, /// The network connection is down. - @JsonValue(2) serviceUnavailable, /// The billing API version is not supported for the type requested. - @JsonValue(3) billingUnavailable, /// The requested product is not available for purchase. - @JsonValue(4) itemUnavailable, /// Invalid arguments provided to the API. - @JsonValue(5) developerError, /// Fatal error during the API action. - @JsonValue(6) error, /// Failure to purchase since item is already owned. - @JsonValue(7) itemAlreadyOwned, /// Failure to consume since item is not owned. - @JsonValue(8) itemNotOwned, /// Network connection failure between the device and Play systems. - @JsonValue(12) networkError, } @@ -445,186 +421,30 @@ enum BillingResponse { /// /// [playBillingOnly] (google Play billing only). /// [alternativeBillingOnly] (app provided billing with reporting to Play). -@JsonEnum(alwaysCreate: true) enum BillingChoiceMode { - // WARNING: Changes to this class need to be reflected in our generated code. - // Run `flutter packages pub run build_runner watch` to rebuild and watch for - // further changes. - // Values must match what is used in - // in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/MethodCallHandlerImpl.java - /// Billing through google Play. Default state. - @JsonValue(0) playBillingOnly, /// Billing through app provided flow. - @JsonValue(1) alternativeBillingOnly, /// Users can choose Play billing or alternative billing. - @JsonValue(2) userChoiceBilling, } -/// Serializer for [BillingChoiceMode]. -/// -/// Use these in `@JsonSerializable()` classes by annotating them with -/// `@BillingChoiceModeConverter()`. -class BillingChoiceModeConverter - implements JsonConverter { - /// Default const constructor. - const BillingChoiceModeConverter(); - - @override - @Deprecated('JSON serialization is not intended for public use, and will ' - 'be removed in a future version.') - BillingChoiceMode fromJson(int? json) { - if (json == null) { - return BillingChoiceMode.playBillingOnly; - } - return $enumDecode(_$BillingChoiceModeEnumMap, json); - } - - @override - int toJson(BillingChoiceMode object) => _$BillingChoiceModeEnumMap[object]!; -} - -/// Serializer for [BillingResponse]. -/// -/// Use these in `@JsonSerializable()` classes by annotating them with -/// `@BillingResponseConverter()`. -class BillingResponseConverter implements JsonConverter { - /// Default const constructor. - const BillingResponseConverter(); - - @override - @Deprecated('JSON serialization is not intended for public use, and will ' - 'be removed in a future version.') - BillingResponse fromJson(int? json) { - if (json == null) { - return BillingResponse.error; - } - return $enumDecode(_$BillingResponseEnumMap, json); - } - - @override - int toJson(BillingResponse object) => _$BillingResponseEnumMap[object]!; -} - /// Enum representing potential [ProductDetailsWrapper.productType]s. /// /// Wraps /// [`BillingClient.ProductType`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.ProductType) /// See the linked documentation for an explanation of the different constants. -@JsonEnum(alwaysCreate: true) enum ProductType { - // WARNING: Changes to this class need to be reflected in our generated code. - // Run `flutter packages pub run build_runner watch` to rebuild and watch for - // further changes. - /// A one time product. Acquired in a single transaction. - @JsonValue('inapp') inapp, /// A product requiring a recurring charge over time. - @JsonValue('subs') subs, } -/// Serializer for [ProductType]. -/// -/// Use these in `@JsonSerializable()` classes by annotating them with -/// `@ProductTypeConverter()`. -class ProductTypeConverter implements JsonConverter { - /// Default const constructor. - const ProductTypeConverter(); - - @override - @Deprecated('JSON serialization is not intended for public use, and will ' - 'be removed in a future version.') - ProductType fromJson(String? json) { - if (json == null) { - return ProductType.inapp; - } - return $enumDecode(_$ProductTypeEnumMap, json); - } - - @override - String toJson(ProductType object) => _$ProductTypeEnumMap[object]!; -} - -/// Enum representing the proration mode. -/// -/// When upgrading or downgrading a subscription, set this mode to provide details -/// about the proration that will be applied when the subscription changes. -/// -/// Wraps [`BillingFlowParams.ProrationMode`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.ProrationMode) -/// See the linked documentation for an explanation of the different constants. -@JsonEnum(alwaysCreate: true) -enum ProrationMode { -// WARNING: Changes to this class need to be reflected in our generated code. -// Run `flutter packages pub run build_runner watch` to rebuild and watch for -// further changes. - - /// Unknown upgrade or downgrade policy. - @JsonValue(0) - unknownSubscriptionUpgradeDowngradePolicy, - - /// Replacement takes effect immediately, and the remaining time will be prorated - /// and credited to the user. - /// - /// This is the current default behavior. - @JsonValue(1) - immediateWithTimeProration, - - /// Replacement takes effect immediately, and the billing cycle remains the same. - /// - /// The price for the remaining period will be charged. - /// This option is only available for subscription upgrade. - @JsonValue(2) - immediateAndChargeProratedPrice, - - /// Replacement takes effect immediately, and the new price will be charged on next - /// recurrence time. - /// - /// The billing cycle stays the same. - @JsonValue(3) - immediateWithoutProration, - - /// Replacement takes effect when the old plan expires, and the new price will - /// be charged at the same time. - @JsonValue(4) - deferred, - - /// Replacement takes effect immediately, and the user is charged full price - /// of new plan and is given a full billing cycle of subscription, plus - /// remaining prorated time from the old plan. - @JsonValue(5) - immediateAndChargeFullPrice, -} - -/// Serializer for [ProrationMode]. -/// -/// Use these in `@JsonSerializable()` classes by annotating them with -/// `@ProrationModeConverter()`. -class ProrationModeConverter implements JsonConverter { - /// Default const constructor. - const ProrationModeConverter(); - - @override - @Deprecated('JSON serialization is not intended for public use, and will ' - 'be removed in a future version.') - ProrationMode fromJson(int? json) { - if (json == null) { - return ProrationMode.unknownSubscriptionUpgradeDowngradePolicy; - } - return $enumDecode(_$ProrationModeEnumMap, json); - } - - @override - int toJson(ProrationMode object) => _$ProrationModeEnumMap[object]!; -} - /// Enum representing the replacement mode. /// /// When upgrading or downgrading a subscription, set this mode to provide details @@ -632,122 +452,61 @@ class ProrationModeConverter implements JsonConverter { /// /// Wraps [`BillingFlowParams.SubscriptionUpdateParams.ReplacementMode`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.ReplacementMode) /// See the linked documentation for an explanation of the different constants. -@JsonEnum(alwaysCreate: true) enum ReplacementMode { -// WARNING: Changes to this class need to be reflected in our generated code. -// Run `flutter packages pub run build_runner watch` to rebuild and watch for -// further changes. - /// Unknown upgrade or downgrade policy. - @JsonValue(0) unknownReplacementMode, /// Replacement takes effect immediately, and the remaining time will be prorated /// and credited to the user. /// /// This is the current default behavior. - @JsonValue(1) withTimeProration, /// Replacement takes effect immediately, and the billing cycle remains the same. /// /// The price for the remaining period will be charged. /// This option is only available for subscription upgrade. - @JsonValue(2) chargeProratedPrice, /// Replacement takes effect immediately, and the new price will be charged on next /// recurrence time. /// /// The billing cycle stays the same. - @JsonValue(3) withoutProration, /// Replacement takes effect when the old plan expires, and the new price will /// be charged at the same time. - @JsonValue(6) deferred, /// Replacement takes effect immediately, and the user is charged full price /// of new plan and is given a full billing cycle of subscription, plus /// remaining prorated time from the old plan. - @JsonValue(5) chargeFullPrice, } -/// Serializer for [ReplacementMode]. -/// -/// Use these in `@JsonSerializable()` classes by annotating them with -/// `@ReplacementModeConverter()`. -class ReplacementModeConverter implements JsonConverter { - /// Default const constructor. - const ReplacementModeConverter(); - - @override - ReplacementMode fromJson(int? json) { - if (json == null) { - return ReplacementMode.unknownReplacementMode; - } - return $enumDecode(_$ReplacementModeEnumMap, json); - } - - @override - int toJson(ReplacementMode object) => _$ReplacementModeEnumMap[object]!; -} - /// Features/capabilities supported by [BillingClient.isFeatureSupported()](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.FeatureType). -@JsonEnum(alwaysCreate: true) enum BillingClientFeature { - // WARNING: Changes to this class need to be reflected in our generated code. - // Run `flutter packages pub run build_runner watch` to rebuild and watch for - // further changes. - // - // JsonValues need to match constant values defined in https://developer.android.com/reference/com/android/billingclient/api/BillingClient.FeatureType#summary + /// Alternative billing only. + alternativeBillingOnly, - /// Purchase/query for in-app items on VR. - @JsonValue('inAppItemsOnVr') - inAppItemsOnVR, + /// Get billing config. + billingConfig, + + /// Play billing library support for external offer. + externalOffer, + + /// Show in-app messages. + inAppMessaging, /// Launch a price change confirmation flow. - @JsonValue('priceChangeConfirmation') priceChangeConfirmation, - /// Play billing library support for querying and purchasing with ProductDetails. - @JsonValue('fff') + /// Play billing library support for querying and purchasing. productDetails, /// Purchase/query for subscriptions. - @JsonValue('subscriptions') subscriptions, - /// Purchase/query for subscriptions on VR. - @JsonValue('subscriptionsOnVr') - subscriptionsOnVR, - /// Subscriptions update/replace. - @JsonValue('subscriptionsUpdate') - subscriptionsUpdate -} - -/// Serializer for [BillingClientFeature]. -/// -/// Use these in `@JsonSerializable()` classes by annotating them with -/// `@BillingClientFeatureConverter()`. -class BillingClientFeatureConverter - implements JsonConverter { - /// Default const constructor. - const BillingClientFeatureConverter(); - - @override - @Deprecated('JSON serialization is not intended for public use, and will ' - 'be removed in a future version.') - BillingClientFeature fromJson(String json) { - return $enumDecode( - _$BillingClientFeatureEnumMap.cast(), - json); - } - - @override - String toJson(BillingClientFeature object) => - _$BillingClientFeatureEnumMap[object]!; + subscriptionsUpdate, } diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_wrapper.g.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_wrapper.g.dart deleted file mode 100644 index eb0033193d9..00000000000 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_wrapper.g.dart +++ /dev/null @@ -1,61 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'billing_client_wrapper.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -const _$BillingResponseEnumMap = { - BillingResponse.serviceTimeout: -3, - BillingResponse.featureNotSupported: -2, - BillingResponse.serviceDisconnected: -1, - BillingResponse.ok: 0, - BillingResponse.userCanceled: 1, - BillingResponse.serviceUnavailable: 2, - BillingResponse.billingUnavailable: 3, - BillingResponse.itemUnavailable: 4, - BillingResponse.developerError: 5, - BillingResponse.error: 6, - BillingResponse.itemAlreadyOwned: 7, - BillingResponse.itemNotOwned: 8, - BillingResponse.networkError: 12, -}; - -const _$BillingChoiceModeEnumMap = { - BillingChoiceMode.playBillingOnly: 0, - BillingChoiceMode.alternativeBillingOnly: 1, - BillingChoiceMode.userChoiceBilling: 2, -}; - -const _$ProductTypeEnumMap = { - ProductType.inapp: 'inapp', - ProductType.subs: 'subs', -}; - -const _$ProrationModeEnumMap = { - ProrationMode.unknownSubscriptionUpgradeDowngradePolicy: 0, - ProrationMode.immediateWithTimeProration: 1, - ProrationMode.immediateAndChargeProratedPrice: 2, - ProrationMode.immediateWithoutProration: 3, - ProrationMode.deferred: 4, - ProrationMode.immediateAndChargeFullPrice: 5, -}; - -const _$ReplacementModeEnumMap = { - ReplacementMode.unknownReplacementMode: 0, - ReplacementMode.withTimeProration: 1, - ReplacementMode.chargeProratedPrice: 2, - ReplacementMode.withoutProration: 3, - ReplacementMode.deferred: 6, - ReplacementMode.chargeFullPrice: 5, -}; - -const _$BillingClientFeatureEnumMap = { - BillingClientFeature.inAppItemsOnVR: 'inAppItemsOnVr', - BillingClientFeature.priceChangeConfirmation: 'priceChangeConfirmation', - BillingClientFeature.productDetails: 'fff', - BillingClientFeature.subscriptions: 'subscriptions', - BillingClientFeature.subscriptionsOnVR: 'subscriptionsOnVr', - BillingClientFeature.subscriptionsUpdate: 'subscriptionsUpdate', -}; diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_config_wrapper.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_config_wrapper.dart index 3020a120e26..1062466932e 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_config_wrapper.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_config_wrapper.dart @@ -3,15 +3,9 @@ // found in the LICENSE file. import 'package:flutter/material.dart'; -import 'package:json_annotation/json_annotation.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 -// rebuild and watch for further changes. -part 'billing_config_wrapper.g.dart'; - /// The error message shown when the map represents billing config is invalid from method channel. /// /// This usually indicates a serious underlining code issue in the plugin. @@ -20,30 +14,12 @@ const String kInvalidBillingConfigErrorMessage = 'Invalid billing config map from method channel.'; /// Params containing the response code and the debug message from the Play Billing API response. -@JsonSerializable() -@BillingResponseConverter() @immutable class BillingConfigWrapper implements HasBillingResponse { /// Constructs the object with [responseCode] and [debugMessage]. const BillingConfigWrapper( {required this.responseCode, this.debugMessage, this.countryCode = ''}); - /// Constructs an instance of this from a key value map of data. - /// - /// The map needs to have named string keys with values matching the names and - /// types of all of the members on this class. - @Deprecated('JSON serialization is not intended for public use, and will ' - 'be removed in a future version.') - factory BillingConfigWrapper.fromJson(Map? map) { - if (map == null || map.isEmpty) { - return const BillingConfigWrapper( - responseCode: BillingResponse.error, - debugMessage: kInvalidBillingConfigErrorMessage, - ); - } - return _$BillingConfigWrapperFromJson(map); - } - /// Response code returned in the Play Billing API calls. @override final BillingResponse responseCode; @@ -52,11 +28,9 @@ class BillingConfigWrapper implements HasBillingResponse { /// /// Defaults to `null`. /// This message uses an en-US locale and should not be shown to users. - @JsonKey(defaultValue: '') final String? debugMessage; /// https://developer.android.com/reference/com/android/billingclient/api/BillingConfig#getCountryCode() - @JsonKey(defaultValue: '') final String countryCode; @override diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_config_wrapper.g.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_config_wrapper.g.dart deleted file mode 100644 index fc8328aec17..00000000000 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_config_wrapper.g.dart +++ /dev/null @@ -1,15 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'billing_config_wrapper.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -BillingConfigWrapper _$BillingConfigWrapperFromJson(Map json) => - BillingConfigWrapper( - responseCode: const BillingResponseConverter() - .fromJson((json['responseCode'] as num?)?.toInt()), - debugMessage: json['debugMessage'] as String? ?? '', - countryCode: json['countryCode'] as String? ?? '', - ); diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_response_wrapper.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_response_wrapper.dart index 5faf19764f8..eb93fa6c068 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_response_wrapper.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_response_wrapper.dart @@ -3,15 +3,9 @@ // found in the LICENSE file. import 'package:flutter/material.dart'; -import 'package:json_annotation/json_annotation.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 -// rebuild and watch for further changes. -part 'billing_response_wrapper.g.dart'; - /// The error message shown when the map represents billing result is invalid from method channel. /// /// This usually indicates a serious underlining code issue in the plugin. @@ -20,28 +14,11 @@ const String kInvalidBillingResultErrorMessage = 'Invalid billing result map from method channel.'; /// Params containing the response code and the debug message from the Play Billing API response. -@JsonSerializable() -@BillingResponseConverter() @immutable class BillingResultWrapper implements HasBillingResponse { /// Constructs the object with [responseCode] and [debugMessage]. const BillingResultWrapper({required this.responseCode, this.debugMessage}); - /// Constructs an instance of this from a key value map of data. - /// - /// The map needs to have named string keys with values matching the names and - /// types of all of the members on this class. - @Deprecated('JSON serialization is not intended for public use, and will ' - 'be removed in a future version.') - factory BillingResultWrapper.fromJson(Map? map) { - if (map == null || map.isEmpty) { - return const BillingResultWrapper( - responseCode: BillingResponse.error, - debugMessage: kInvalidBillingResultErrorMessage); - } - return _$BillingResultWrapperFromJson(map); - } - /// Response code returned in the Play Billing API calls. @override final BillingResponse responseCode; diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_response_wrapper.g.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_response_wrapper.g.dart deleted file mode 100644 index a3b28403983..00000000000 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_response_wrapper.g.dart +++ /dev/null @@ -1,14 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'billing_response_wrapper.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -BillingResultWrapper _$BillingResultWrapperFromJson(Map json) => - BillingResultWrapper( - responseCode: const BillingResponseConverter() - .fromJson((json['responseCode'] as num?)?.toInt()), - debugMessage: json['debugMessage'] as String?, - ); diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/one_time_purchase_offer_details_wrapper.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/one_time_purchase_offer_details_wrapper.dart index f2218495883..8dfebeee73e 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/one_time_purchase_offer_details_wrapper.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/one_time_purchase_offer_details_wrapper.dart @@ -3,17 +3,10 @@ // found in the LICENSE file. import 'package:flutter/foundation.dart'; -import 'package:json_annotation/json_annotation.dart'; - -// WARNING: Changes to `@JsonSerializable` classes need to be reflected in the -// below generated file. Run `flutter packages pub run build_runner watch` to -// rebuild and watch for further changes. -part 'one_time_purchase_offer_details_wrapper.g.dart'; /// Dart wrapper around [`com.android.billingclient.api.ProductDetails.OneTimePurchaseOfferDetails`](https://developer.android.com/reference/com/android/billingclient/api/ProductDetails.OneTimePurchaseOfferDetails). /// /// Represents the offer details to buy a one-time purchase product. -@JsonSerializable() @immutable class OneTimePurchaseOfferDetailsWrapper { /// Creates a [OneTimePurchaseOfferDetailsWrapper]. @@ -23,18 +16,9 @@ class OneTimePurchaseOfferDetailsWrapper { required this.priceCurrencyCode, }); - /// Factory for creating a [OneTimePurchaseOfferDetailsWrapper] from a [Map] - /// with the offer details. - @Deprecated('JSON serialization is not intended for public use, and will ' - 'be removed in a future version.') - factory OneTimePurchaseOfferDetailsWrapper.fromJson( - Map map) => - _$OneTimePurchaseOfferDetailsWrapperFromJson(map); - /// Formatted price for the payment, including its currency sign. /// /// For tax exclusive countries, the price doesn't include tax. - @JsonKey(defaultValue: '') final String formattedPrice; /// The price for the payment in micro-units, where 1,000,000 micro-units @@ -42,14 +26,12 @@ class OneTimePurchaseOfferDetailsWrapper { /// /// For example, if price is "€7.99", price_amount_micros is "7990000". This /// value represents the localized, rounded price for a particular currency. - @JsonKey(defaultValue: 0) final int priceAmountMicros; /// The ISO 4217 currency code for price. /// /// For example, if price is specified in British pounds sterling, currency /// code is "GBP". - @JsonKey(defaultValue: '') final String priceCurrencyCode; @override diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/one_time_purchase_offer_details_wrapper.g.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/one_time_purchase_offer_details_wrapper.g.dart deleted file mode 100644 index f26a38162fc..00000000000 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/one_time_purchase_offer_details_wrapper.g.dart +++ /dev/null @@ -1,15 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'one_time_purchase_offer_details_wrapper.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -OneTimePurchaseOfferDetailsWrapper _$OneTimePurchaseOfferDetailsWrapperFromJson( - Map json) => - OneTimePurchaseOfferDetailsWrapper( - formattedPrice: json['formattedPrice'] as String? ?? '', - priceAmountMicros: (json['priceAmountMicros'] as num?)?.toInt() ?? 0, - priceCurrencyCode: json['priceCurrencyCode'] as String? ?? '', - ); diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/pending_purchases_params_wrapper.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/pending_purchases_params_wrapper.dart new file mode 100644 index 00000000000..d702cae27bf --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/pending_purchases_params_wrapper.dart @@ -0,0 +1,39 @@ +// 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'; + +/// Dart wrapper around [`com.android.billingclient.api.PendingPurchasesParams`](https://developer.android.com/reference/com/android/billingclient/api/PendingPurchasesParams). +/// +/// Represents the parameters to enable pending purchases. +@immutable +class PendingPurchasesParamsWrapper { + /// Creates a [PendingPurchasesParamsWrapper]. + const PendingPurchasesParamsWrapper({ + required this.enablePrepaidPlans, + }); + + /// Enables pending purchase for prepaid plans. + /// + /// Handling pending purchases for prepaid plans is different from one-time products. + /// Your application will need to be updated to ensure subscription entitlements are + /// managed correctly with pending transactions. + /// To learn more see https://developer.android.com/google/play/billing/subscriptions#pending. + final bool enablePrepaidPlans; + + @override + bool operator ==(Object other) { + if (other.runtimeType != runtimeType) { + return false; + } + + return other is PendingPurchasesParamsWrapper && + other.enablePrepaidPlans == enablePrepaidPlans; + } + + @override + int get hashCode { + return enablePrepaidPlans.hashCode; + } +} diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/product_details_wrapper.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/product_details_wrapper.dart index 43303d4d237..7059d496a16 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/product_details_wrapper.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/product_details_wrapper.dart @@ -3,21 +3,13 @@ // found in the LICENSE file. import 'package:flutter/foundation.dart'; -import 'package:json_annotation/json_annotation.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 -// rebuild and watch for further changes. -part 'product_details_wrapper.g.dart'; - /// Dart wrapper around [`com.android.billingclient.api.ProductDetails`](https://developer.android.com/reference/com/android/billingclient/api/ProductDetails). /// /// Contains the details of an available product in Google Play Billing. /// Represents the details of a one-time or subscription product. -@JsonSerializable() -@ProductTypeConverter() @immutable class ProductDetailsWrapper { /// Creates a [ProductDetailsWrapper] with the given purchase details. @@ -31,51 +23,37 @@ class ProductDetailsWrapper { required this.title, }); - /// Factory for creating a [ProductDetailsWrapper] from a [Map] with the - /// product details. - @Deprecated('JSON serialization is not intended for public use, and will ' - 'be removed in a future version.') - factory ProductDetailsWrapper.fromJson(Map map) => - _$ProductDetailsWrapperFromJson(map); - /// Textual description of the product. - @JsonKey(defaultValue: '') final String description; /// The name of the product being sold. /// /// Similar to [title], but does not include the name of the app which owns /// the product. Example: 100 Gold Coins. - @JsonKey(defaultValue: '') final String name; /// The offer details of a one-time purchase product. /// /// [oneTimePurchaseOfferDetails] is only set for [ProductType.inapp]. Returns /// null for [ProductType.subs]. - @JsonKey(defaultValue: null) final OneTimePurchaseOfferDetailsWrapper? oneTimePurchaseOfferDetails; /// The product's id. - @JsonKey(defaultValue: '') final String productId; /// The [ProductType] of the product. - @JsonKey(defaultValue: ProductType.subs) final ProductType productType; /// A list containing all available offers to purchase a subscription product. /// /// [subscriptionOfferDetails] is only set for [ProductType.subs]. Returns /// null for [ProductType.inapp]. - @JsonKey(defaultValue: null) final List? subscriptionOfferDetails; /// The title of the product being sold. /// /// Similar to [name], but includes the name of the app which owns the /// product. Example: 100 Gold Coins (Coin selling app). - @JsonKey(defaultValue: '') final String title; @override @@ -111,7 +89,6 @@ class ProductDetailsWrapper { /// Translation of [`com.android.billingclient.api.ProductDetailsResponseListener`](https://developer.android.com/reference/com/android/billingclient/api/ProductDetailsResponseListener.html). /// /// Returned by [BillingClient.queryProductDetails]. -@JsonSerializable() @immutable class ProductDetailsResponseWrapper implements HasBillingResponse { /// Creates a [ProductDetailsResponseWrapper] with the given purchase details. @@ -120,20 +97,10 @@ class ProductDetailsResponseWrapper implements HasBillingResponse { required this.productDetailsList, }); - /// Constructs an instance of this from a key value map of data. - /// - /// The map needs to have named string keys with values matching the names and - /// types of all of the members on this class. - @Deprecated('JSON serialization is not intended for public use, and will ' - 'be removed in a future version.') - factory ProductDetailsResponseWrapper.fromJson(Map map) => - _$ProductDetailsResponseWrapperFromJson(map); - /// The final result of the [BillingClient.queryProductDetails] call. final BillingResultWrapper billingResult; /// A list of [ProductDetailsWrapper] matching the query to [BillingClient.queryProductDetails]. - @JsonKey(defaultValue: []) final List productDetailsList; @override @@ -155,41 +122,15 @@ class ProductDetailsResponseWrapper implements HasBillingResponse { } /// Recurrence mode of the pricing phase. -@JsonEnum(alwaysCreate: true) enum RecurrenceMode { /// The billing plan payment recurs for a fixed number of billing period set /// in billingCycleCount. - @JsonValue(2) finiteRecurring, /// The billing plan payment recurs for infinite billing periods unless /// cancelled. - @JsonValue(1) infiniteRecurring, /// The billing plan payment is a one time charge that does not repeat. - @JsonValue(3) nonRecurring, } - -/// Serializer for [RecurrenceMode]. -/// -/// Use these in `@JsonSerializable()` classes by annotating them with -/// `@RecurrenceModeConverter()`. -class RecurrenceModeConverter implements JsonConverter { - /// Default const constructor. - const RecurrenceModeConverter(); - - @override - @Deprecated('JSON serialization is not intended for public use, and will ' - 'be removed in a future version.') - RecurrenceMode fromJson(int? json) { - if (json == null) { - return RecurrenceMode.nonRecurring; - } - return $enumDecode(_$RecurrenceModeEnumMap, json); - } - - @override - int toJson(RecurrenceMode object) => _$RecurrenceModeEnumMap[object]!; -} diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/product_details_wrapper.g.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/product_details_wrapper.g.dart deleted file mode 100644 index de8079d8157..00000000000 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/product_details_wrapper.g.dart +++ /dev/null @@ -1,49 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'product_details_wrapper.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -ProductDetailsWrapper _$ProductDetailsWrapperFromJson(Map json) => - ProductDetailsWrapper( - description: json['description'] as String? ?? '', - name: json['name'] as String? ?? '', - oneTimePurchaseOfferDetails: json['oneTimePurchaseOfferDetails'] == null - ? null - : OneTimePurchaseOfferDetailsWrapper.fromJson( - Map.from( - json['oneTimePurchaseOfferDetails'] as Map)), - productId: json['productId'] as String? ?? '', - productType: json['productType'] == null - ? ProductType.subs - : const ProductTypeConverter() - .fromJson(json['productType'] as String?), - subscriptionOfferDetails: - (json['subscriptionOfferDetails'] as List?) - ?.map((e) => SubscriptionOfferDetailsWrapper.fromJson( - Map.from(e as Map))) - .toList(), - title: json['title'] as String? ?? '', - ); - -ProductDetailsResponseWrapper _$ProductDetailsResponseWrapperFromJson( - Map json) => - ProductDetailsResponseWrapper( - billingResult: - BillingResultWrapper.fromJson((json['billingResult'] as Map?)?.map( - (k, e) => MapEntry(k as String, e), - )), - productDetailsList: (json['productDetailsList'] as List?) - ?.map((e) => ProductDetailsWrapper.fromJson( - Map.from(e as Map))) - .toList() ?? - [], - ); - -const _$RecurrenceModeEnumMap = { - RecurrenceMode.finiteRecurring: 2, - RecurrenceMode.infiniteRecurring: 1, - RecurrenceMode.nonRecurring: 3, -}; diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/product_wrapper.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/product_wrapper.dart index 48cd9ee738e..65833bcb61c 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/product_wrapper.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/product_wrapper.dart @@ -3,17 +3,10 @@ // found in the LICENSE file. import 'package:flutter/foundation.dart'; -import 'package:json_annotation/json_annotation.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 -// rebuild and watch for further changes. -part 'product_wrapper.g.dart'; - /// Dart wrapper around [`com.android.billingclient.api.Product`](https://developer.android.com/reference/com/android/billingclient/api/QueryProductDetailsParams.Product). -@JsonSerializable(createToJson: true) @immutable class ProductWrapper { /// Creates a new [ProductWrapper]. @@ -22,11 +15,7 @@ class ProductWrapper { required this.productType, }); - /// Creates a JSON representation of this product. - Map toJson() => _$ProductWrapperToJson(this); - /// The product identifier. - @JsonKey(defaultValue: '') final String productId; /// The product type. diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/product_wrapper.g.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/product_wrapper.g.dart deleted file mode 100644 index c3ba8f4a82e..00000000000 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/product_wrapper.g.dart +++ /dev/null @@ -1,23 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'product_wrapper.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -ProductWrapper _$ProductWrapperFromJson(Map json) => ProductWrapper( - productId: json['productId'] as String? ?? '', - productType: $enumDecode(_$ProductTypeEnumMap, json['productType']), - ); - -Map _$ProductWrapperToJson(ProductWrapper instance) => - { - 'productId': instance.productId, - 'productType': _$ProductTypeEnumMap[instance.productType]!, - }; - -const _$ProductTypeEnumMap = { - ProductType.inapp: 'inapp', - ProductType.subs: 'subs', -}; 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 8d17d81c9a4..1572be847cf 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 @@ -4,15 +4,9 @@ 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_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 -// rebuild and watch for further changes. -part 'purchase_wrapper.g.dart'; - /// Data structure representing a successful purchase. /// /// All purchase information should also be verified manually, with your @@ -20,8 +14,6 @@ part 'purchase_wrapper.g.dart'; /// purchase"](https://developer.android.com/google/play/billing/billing_library_overview#Verify). /// /// This wraps [`com.android.billlingclient.api.Purchase`](https://developer.android.com/reference/com/android/billingclient/api/Purchase) -@JsonSerializable() -@PurchaseStateConverter() @immutable class PurchaseWrapper { /// Creates a purchase wrapper with the given purchase details. @@ -39,14 +31,9 @@ class PurchaseWrapper { required this.purchaseState, this.obfuscatedAccountId, this.obfuscatedProfileId, + this.pendingPurchaseUpdate, }); - /// Factory for creating a [PurchaseWrapper] from a [Map] with the purchase details. - @Deprecated('JSON serialization is not intended for public use, and will ' - 'be removed in a future version.') - factory PurchaseWrapper.fromJson(Map map) => - _$PurchaseWrapperFromJson(map); - @override bool operator ==(Object other) { if (identical(other, this)) { @@ -65,7 +52,8 @@ class PurchaseWrapper { other.isAutoRenewing == isAutoRenewing && other.originalJson == originalJson && other.isAcknowledged == isAcknowledged && - other.purchaseState == purchaseState; + other.purchaseState == purchaseState && + other.pendingPurchaseUpdate == pendingPurchaseUpdate; } @override @@ -79,32 +67,27 @@ class PurchaseWrapper { isAutoRenewing, originalJson, isAcknowledged, - purchaseState); + purchaseState, + pendingPurchaseUpdate); /// The unique ID for this purchase. Corresponds to the Google Payments order /// ID. - @JsonKey(defaultValue: '') final String orderId; /// The package name the purchase was made from. - @JsonKey(defaultValue: '') final String packageName; /// When the purchase was made, as an epoch timestamp. - @JsonKey(defaultValue: 0) final int purchaseTime; /// A unique ID for a given [ProductDetailsWrapper], user, and purchase. - @JsonKey(defaultValue: '') final String purchaseToken; /// Signature of purchase data, signed with the developer's private key. Uses /// RSASSA-PKCS1-v1_5. - @JsonKey(defaultValue: '') final String signature; /// The product IDs of this purchase. - @JsonKey(defaultValue: []) final List products; /// True for subscriptions that renew automatically. Does not apply to @@ -122,7 +105,6 @@ class PurchaseWrapper { /// device"](https://developer.android.com/google/play/billing/billing_library_overview#Verify-purchase-device). /// Note though that verifying a purchase locally is inherently insecure (see /// the article for more details). - @JsonKey(defaultValue: '') final String originalJson; /// The payload specified by the developer when the purchase was acknowledged or consumed. @@ -136,7 +118,6 @@ class PurchaseWrapper { /// /// A successful purchase has to be acknowledged within 3 days after the purchase via [BillingClient.acknowledgePurchase]. /// * See also [BillingClient.acknowledgePurchase] for more details on acknowledging purchases. - @JsonKey(defaultValue: false) final bool isAcknowledged; /// Determines the current state of the purchase. @@ -158,6 +139,51 @@ class PurchaseWrapper { /// directly calling [BillingClient.launchBillingFlow] and is not available /// on the generic [InAppPurchasePlatform]. final String? obfuscatedProfileId; + + /// The [PendingPurchaseUpdateWrapper] for an uncommitted transaction. + /// + /// A PendingPurchaseUpdate is normally generated from a pending transaction + /// upgrading/downgrading an existing subscription. + /// Returns null if this purchase does not have a pending transaction. + final PendingPurchaseUpdateWrapper? pendingPurchaseUpdate; +} + +@immutable + +/// Represents a pending change/update to the existing purchase. +/// +/// This wraps [`com.android.billingclient.api.Purchase.PendingPurchaseUpdate`](https://developer.android.com/reference/com/android/billingclient/api/Purchase.PendingPurchaseUpdate). +class PendingPurchaseUpdateWrapper { + /// Creates a pending purchase wrapper update wrapper with the given purchase details. + const PendingPurchaseUpdateWrapper({ + required this.purchaseToken, + required this.products, + }); + + /// A token that uniquely identifies this pending transaction. + final String purchaseToken; + + /// The product IDs of this pending purchase update. + final List products; + + @override + bool operator ==(Object other) { + if (identical(other, this)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + return other is PendingPurchaseUpdateWrapper && + other.purchaseToken == purchaseToken && + listEquals(other.products, products); + } + + @override + int get hashCode => Object.hash( + purchaseToken, + products.hashCode, + ); } /// Data structure representing a purchase history record. @@ -169,7 +195,6 @@ class PurchaseWrapper { /// * See also: [BillingClient.queryPurchaseHistory] for obtaining a [PurchaseHistoryRecordWrapper]. // We can optionally make [PurchaseWrapper] extend or implement [PurchaseHistoryRecordWrapper]. // For now, we keep them separated classes to be consistent with Android's BillingClient implementation. -@JsonSerializable() @immutable class PurchaseHistoryRecordWrapper { /// Creates a [PurchaseHistoryRecordWrapper] with the given record details. @@ -182,27 +207,17 @@ class PurchaseHistoryRecordWrapper { required this.developerPayload, }); - /// Factory for creating a [PurchaseHistoryRecordWrapper] from a [Map] with the record details. - @Deprecated('JSON serialization is not intended for public use, and will ' - 'be removed in a future version.') - factory PurchaseHistoryRecordWrapper.fromJson(Map map) => - _$PurchaseHistoryRecordWrapperFromJson(map); - /// When the purchase was made, as an epoch timestamp. - @JsonKey(defaultValue: 0) final int purchaseTime; /// A unique ID for a given [ProductDetailsWrapper], user, and purchase. - @JsonKey(defaultValue: '') final String purchaseToken; /// Signature of purchase data, signed with the developer's private key. Uses /// RSASSA-PKCS1-v1_5. - @JsonKey(defaultValue: '') final String signature; /// The product ID of this purchase. - @JsonKey(defaultValue: []) final List products; /// Details about this purchase, in JSON. @@ -211,7 +226,6 @@ class PurchaseHistoryRecordWrapper { /// device"](https://developer.android.com/google/play/billing/billing_library_overview#Verify-purchase-device). /// Note though that verifying a purchase locally is inherently insecure (see /// the article for more details). - @JsonKey(defaultValue: '') final String originalJson; /// The payload specified by the developer when the purchase was acknowledged or consumed. @@ -254,8 +268,6 @@ class PurchaseHistoryRecordWrapper { /// [BillingResponse] to signify the overall state of the transaction. /// /// Wraps [`com.android.billingclient.api.Purchase.PurchasesResult`](https://developer.android.com/reference/com/android/billingclient/api/Purchase.PurchasesResult). -@JsonSerializable() -@BillingResponseConverter() @immutable class PurchasesResultWrapper implements HasBillingResponse { /// Creates a [PurchasesResultWrapper] with the given purchase result details. @@ -264,12 +276,6 @@ class PurchasesResultWrapper implements HasBillingResponse { required this.billingResult, required this.purchasesList}); - /// Factory for creating a [PurchaseResultWrapper] from a [Map] with the result details. - @Deprecated('JSON serialization is not intended for public use, and will ' - 'be removed in a future version.') - factory PurchasesResultWrapper.fromJson(Map map) => - _$PurchasesResultWrapperFromJson(map); - @override bool operator ==(Object other) { if (identical(other, this)) { @@ -300,7 +306,6 @@ class PurchasesResultWrapper implements HasBillingResponse { /// The list of successful purchases made in this transaction. /// /// May be empty, especially if [responseCode] is not [BillingResponse.ok]. - @JsonKey(defaultValue: []) final List purchasesList; } @@ -308,20 +313,12 @@ class PurchasesResultWrapper implements HasBillingResponse { /// /// Contains a potentially empty list of [PurchaseHistoryRecordWrapper]s and a [BillingResultWrapper] /// that contains a detailed description of the status. -@JsonSerializable() -@BillingResponseConverter() @immutable class PurchasesHistoryResult implements HasBillingResponse { /// Creates a [PurchasesHistoryResult] with the provided history. const PurchasesHistoryResult( {required this.billingResult, required this.purchaseHistoryRecordList}); - /// Factory for creating a [PurchasesHistoryResult] from a [Map] with the history result details. - @Deprecated('JSON serialization is not intended for public use, and will ' - 'be removed in a future version.') - factory PurchasesHistoryResult.fromJson(Map map) => - _$PurchasesHistoryResultFromJson(map); - @override BillingResponse get responseCode => billingResult.responseCode; @@ -347,7 +344,6 @@ class PurchasesHistoryResult implements HasBillingResponse { /// The list of queried purchase history records. /// /// May be empty, especially if [billingResult.responseCode] is not [BillingResponse.ok]. - @JsonKey(defaultValue: []) final List purchaseHistoryRecordList; } @@ -356,20 +352,17 @@ class PurchasesHistoryResult implements HasBillingResponse { /// Wraps /// [`BillingClient.api.Purchase.PurchaseState`](https://developer.android.com/reference/com/android/billingclient/api/Purchase.PurchaseState.html). /// * See also: [PurchaseWrapper]. -@JsonEnum(alwaysCreate: true) enum PurchaseStateWrapper { /// The state is unspecified. /// /// No actions on the [PurchaseWrapper] should be performed on this state. /// This is a catch-all. It should never be returned by the Play Billing Library. - @JsonValue(0) unspecified_state, /// The user has completed the purchase process. /// /// The production should be delivered and then the purchase should be acknowledged. /// * See also [BillingClient.acknowledgePurchase] for more details on acknowledging purchases. - @JsonValue(1) purchased, /// The user has started the purchase process. @@ -379,44 +372,5 @@ enum PurchaseStateWrapper { /// /// You can also choose to remind the user to complete the purchase if you detected a /// [PurchaseWrapper] is still in the `pending` state in the future while calling [BillingClient.queryPurchases]. - @JsonValue(2) pending, } - -/// Serializer for [PurchaseStateWrapper]. -/// -/// Use these in `@JsonSerializable()` classes by annotating them with -/// `@PurchaseStateConverter()`. -class PurchaseStateConverter - implements JsonConverter { - /// Default const constructor. - const PurchaseStateConverter(); - - @override - @Deprecated('JSON serialization is not intended for public use, and will ' - 'be removed in a future version.') - PurchaseStateWrapper fromJson(int? json) { - if (json == null) { - return PurchaseStateWrapper.unspecified_state; - } - return $enumDecode(_$PurchaseStateWrapperEnumMap, json); - } - - @override - int toJson(PurchaseStateWrapper object) => - _$PurchaseStateWrapperEnumMap[object]!; - - /// Converts the purchase state stored in `object` to a [PurchaseStatus]. - /// - /// [PurchaseStateWrapper.unspecified_state] is mapped to [PurchaseStatus.error]. - PurchaseStatus toPurchaseStatus(PurchaseStateWrapper object) { - switch (object) { - case PurchaseStateWrapper.pending: - return PurchaseStatus.pending; - case PurchaseStateWrapper.purchased: - return PurchaseStatus.purchased; - case PurchaseStateWrapper.unspecified_state: - return PurchaseStatus.error; - } - } -} diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/purchase_wrapper.g.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/purchase_wrapper.g.dart deleted file mode 100644 index b97afce0f62..00000000000 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/purchase_wrapper.g.dart +++ /dev/null @@ -1,75 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'purchase_wrapper.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -PurchaseWrapper _$PurchaseWrapperFromJson(Map json) => PurchaseWrapper( - orderId: json['orderId'] as String? ?? '', - packageName: json['packageName'] as String? ?? '', - purchaseTime: (json['purchaseTime'] as num?)?.toInt() ?? 0, - purchaseToken: json['purchaseToken'] as String? ?? '', - signature: json['signature'] as String? ?? '', - products: (json['products'] as List?) - ?.map((e) => e as String) - .toList() ?? - [], - isAutoRenewing: json['isAutoRenewing'] as bool, - originalJson: json['originalJson'] as String? ?? '', - developerPayload: json['developerPayload'] as String?, - isAcknowledged: json['isAcknowledged'] as bool? ?? false, - purchaseState: const PurchaseStateConverter() - .fromJson((json['purchaseState'] as num?)?.toInt()), - obfuscatedAccountId: json['obfuscatedAccountId'] as String?, - obfuscatedProfileId: json['obfuscatedProfileId'] as String?, - ); - -PurchaseHistoryRecordWrapper _$PurchaseHistoryRecordWrapperFromJson(Map json) => - PurchaseHistoryRecordWrapper( - purchaseTime: (json['purchaseTime'] as num?)?.toInt() ?? 0, - purchaseToken: json['purchaseToken'] as String? ?? '', - signature: json['signature'] as String? ?? '', - products: (json['products'] as List?) - ?.map((e) => e as String) - .toList() ?? - [], - originalJson: json['originalJson'] as String? ?? '', - developerPayload: json['developerPayload'] as String?, - ); - -PurchasesResultWrapper _$PurchasesResultWrapperFromJson(Map json) => - PurchasesResultWrapper( - responseCode: const BillingResponseConverter() - .fromJson((json['responseCode'] as num?)?.toInt()), - billingResult: - BillingResultWrapper.fromJson((json['billingResult'] as Map?)?.map( - (k, e) => MapEntry(k as String, e), - )), - purchasesList: (json['purchasesList'] as List?) - ?.map((e) => - PurchaseWrapper.fromJson(Map.from(e as Map))) - .toList() ?? - [], - ); - -PurchasesHistoryResult _$PurchasesHistoryResultFromJson(Map json) => - PurchasesHistoryResult( - billingResult: - BillingResultWrapper.fromJson((json['billingResult'] as Map?)?.map( - (k, e) => MapEntry(k as String, e), - )), - purchaseHistoryRecordList: - (json['purchaseHistoryRecordList'] as List?) - ?.map((e) => PurchaseHistoryRecordWrapper.fromJson( - Map.from(e as Map))) - .toList() ?? - [], - ); - -const _$PurchaseStateWrapperEnumMap = { - PurchaseStateWrapper.unspecified_state: 0, - PurchaseStateWrapper.purchased: 1, - PurchaseStateWrapper.pending: 2, -}; diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/subscription_offer_details_wrapper.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/subscription_offer_details_wrapper.dart index 4b5642ed043..f31757e922f 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/subscription_offer_details_wrapper.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/subscription_offer_details_wrapper.dart @@ -3,20 +3,13 @@ // found in the LICENSE file. import 'package:flutter/foundation.dart'; -import 'package:json_annotation/json_annotation.dart'; import 'billing_client_wrapper.dart'; import 'product_details_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 -// rebuild and watch for further changes. -part 'subscription_offer_details_wrapper.g.dart'; - /// Dart wrapper around [`com.android.billingclient.api.ProductDetails.SubscriptionOfferDetails`](https://developer.android.com/reference/com/android/billingclient/api/ProductDetails.SubscriptionOfferDetails). /// /// Represents the available purchase plans to buy a subscription product. -@JsonSerializable() @immutable class SubscriptionOfferDetailsWrapper { /// Creates a [SubscriptionOfferDetailsWrapper]. @@ -26,39 +19,31 @@ class SubscriptionOfferDetailsWrapper { required this.offerTags, required this.offerIdToken, required this.pricingPhases, + this.installmentPlanDetails, }); - /// Factory for creating a [SubscriptionOfferDetailsWrapper] from a [Map] - /// with the offer details. - @Deprecated('JSON serialization is not intended for public use, and will ' - 'be removed in a future version.') - factory SubscriptionOfferDetailsWrapper.fromJson(Map map) => - _$SubscriptionOfferDetailsWrapperFromJson(map); - /// The base plan id associated with the subscription product. - @JsonKey(defaultValue: '') final String basePlanId; /// The offer id associated with the subscription product. /// /// This field is only set for a discounted offer. Returns null for a regular /// base plan. - @JsonKey(defaultValue: null) final String? offerId; /// The offer tags associated with this Subscription Offer. - @JsonKey(defaultValue: []) final List offerTags; /// The offer token required to pass in [BillingClient.launchBillingFlow] to /// purchase the subscription product with these [pricingPhases]. - @JsonKey(defaultValue: '') final String offerIdToken; /// The pricing phases for the subscription product. - @JsonKey(defaultValue: []) final List pricingPhases; + /// Represents additional details of an installment subscription plan. + final InstallmentPlanDetailsWrapper? installmentPlanDetails; + @override bool operator ==(Object other) { if (other.runtimeType != runtimeType) { @@ -70,7 +55,8 @@ class SubscriptionOfferDetailsWrapper { other.offerId == offerId && listEquals(other.offerTags, offerTags) && other.offerIdToken == offerIdToken && - listEquals(other.pricingPhases, pricingPhases); + listEquals(other.pricingPhases, pricingPhases) && + installmentPlanDetails == other.installmentPlanDetails; } @override @@ -81,13 +67,12 @@ class SubscriptionOfferDetailsWrapper { offerTags.hashCode, offerIdToken.hashCode, pricingPhases.hashCode, + installmentPlanDetails.hashCode, ); } } /// Represents a pricing phase, describing how a user pays at a point in time. -@JsonSerializable() -@RecurrenceModeConverter() @immutable class PricingPhaseWrapper { /// Creates a new [PricingPhaseWrapper] from the supplied info. @@ -100,37 +85,25 @@ class PricingPhaseWrapper { required this.recurrenceMode, }); - /// Factory for creating a [PricingPhaseWrapper] from a [Map] with the phase details. - @Deprecated('JSON serialization is not intended for public use, and will ' - 'be removed in a future version.') - factory PricingPhaseWrapper.fromJson(Map map) => - _$PricingPhaseWrapperFromJson(map); - /// Represents a pricing phase, describing how a user pays at a point in time. - @JsonKey(defaultValue: 0) final int billingCycleCount; /// Billing period for which the given price applies, specified in ISO 8601 /// format. - @JsonKey(defaultValue: '') final String billingPeriod; /// Returns formatted price for the payment cycle, including its currency /// sign. - @JsonKey(defaultValue: '') final String formattedPrice; /// Returns the price for the payment cycle in micro-units, where 1,000,000 /// micro-units equal one unit of the currency. - @JsonKey(defaultValue: 0) final int priceAmountMicros; /// Returns ISO 4217 currency code for price. - @JsonKey(defaultValue: '') final String priceCurrencyCode; /// Returns [RecurrenceMode] for the pricing phase. - @JsonKey(defaultValue: RecurrenceMode.nonRecurring) final RecurrenceMode recurrenceMode; @override @@ -158,3 +131,53 @@ class PricingPhaseWrapper { recurrenceMode, ); } + +/// Represents additional details of an installment subscription plan. +/// +/// This wraps [`com.android.billingclient.api.ProductDetails.InstallmentPlanDetails`](https://developer.android.com/reference/com/android/billingclient/api/ProductDetails.InstallmentPlanDetails). +@immutable +class InstallmentPlanDetailsWrapper { + /// Creates a [InstallmentPlanDetailsWrapper]. + const InstallmentPlanDetailsWrapper({ + required this.commitmentPaymentsCount, + required this.subsequentCommitmentPaymentsCount, + }); + + /// Committed payments count after a user signs up for this subscription plan. + /// + /// For example, for a monthly subscription plan with commitmentPaymentsCount + /// as 12, users will be charged monthly for 12 month after initial signup. + /// User cancellation won't take effect until all 12 committed payments are finished. + final int commitmentPaymentsCount; + + /// Subsequent committed payments count after this subscription plan renews. + /// + /// For example, for a monthly subscription plan with subsequentCommitmentPaymentsCount + /// as 12, when the subscription plan renews, users will be committed to another fresh + /// 12 monthly payments. + /// + /// Note: Returns 0 if the installment plan doesn't have any subsequent committment, + /// which means this subscription plan will fall back to a normal non-installment + /// monthly plan when the plan renews. + final int subsequentCommitmentPaymentsCount; + + @override + bool operator ==(Object other) { + if (other.runtimeType != runtimeType) { + return false; + } + + return other is InstallmentPlanDetailsWrapper && + other.commitmentPaymentsCount == commitmentPaymentsCount && + other.subsequentCommitmentPaymentsCount == + subsequentCommitmentPaymentsCount; + } + + @override + int get hashCode { + return Object.hash( + commitmentPaymentsCount.hashCode, + subsequentCommitmentPaymentsCount.hashCode, + ); + } +} diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/subscription_offer_details_wrapper.g.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/subscription_offer_details_wrapper.g.dart deleted file mode 100644 index 46973ed7d42..00000000000 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/subscription_offer_details_wrapper.g.dart +++ /dev/null @@ -1,37 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'subscription_offer_details_wrapper.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -SubscriptionOfferDetailsWrapper _$SubscriptionOfferDetailsWrapperFromJson( - Map json) => - SubscriptionOfferDetailsWrapper( - basePlanId: json['basePlanId'] as String? ?? '', - offerId: json['offerId'] as String?, - offerTags: (json['offerTags'] as List?) - ?.map((e) => e as String) - .toList() ?? - [], - offerIdToken: json['offerIdToken'] as String? ?? '', - pricingPhases: (json['pricingPhases'] as List?) - ?.map((e) => PricingPhaseWrapper.fromJson( - Map.from(e as Map))) - .toList() ?? - [], - ); - -PricingPhaseWrapper _$PricingPhaseWrapperFromJson(Map json) => - PricingPhaseWrapper( - billingCycleCount: (json['billingCycleCount'] as num?)?.toInt() ?? 0, - billingPeriod: json['billingPeriod'] as String? ?? '', - formattedPrice: json['formattedPrice'] as String? ?? '', - priceAmountMicros: (json['priceAmountMicros'] as num?)?.toInt() ?? 0, - priceCurrencyCode: json['priceCurrencyCode'] as String? ?? '', - recurrenceMode: json['recurrenceMode'] == null - ? RecurrenceMode.nonRecurring - : const RecurrenceModeConverter() - .fromJson((json['recurrenceMode'] as num?)?.toInt()), - ); diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/user_choice_details_wrapper.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/user_choice_details_wrapper.dart index 8b23058c195..01654519f64 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/user_choice_details_wrapper.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/user_choice_details_wrapper.dart @@ -3,19 +3,12 @@ // found in the LICENSE file. import 'package:flutter/foundation.dart'; -import 'package:json_annotation/json_annotation.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 -// rebuild and watch for further changes. -part 'user_choice_details_wrapper.g.dart'; - /// This wraps [`com.android.billingclient.api.UserChoiceDetails`](https://developer.android.com/reference/com/android/billingclient/api/UserChoiceDetails) // See https://docs.flutter.dev/data-and-backend/serialization/json#generating-code-for-nested-classes // for explination for why this uses explicitToJson. -@JsonSerializable(createToJson: true, explicitToJson: true) @immutable class UserChoiceDetailsWrapper { /// Creates a purchase wrapper with the given purchase details. @@ -25,16 +18,6 @@ class UserChoiceDetailsWrapper { required this.products, }); - /// Factory for creating a [UserChoiceDetailsWrapper] from a [Map] with - /// the user choice details. - @Deprecated('JSON serialization is not intended for public use, and will ' - 'be removed in a future version.') - factory UserChoiceDetailsWrapper.fromJson(Map map) => - _$UserChoiceDetailsWrapperFromJson(map); - - /// Creates a JSON representation of this product. - Map toJson() => _$UserChoiceDetailsWrapperToJson(this); - @override bool operator ==(Object other) { if (identical(other, this)) { @@ -58,17 +41,14 @@ class UserChoiceDetailsWrapper { /// Returns the external transaction Id of the originating subscription, if /// the purchase is a subscription upgrade/downgrade. - @JsonKey(defaultValue: '') final String originalExternalTransactionId; /// Returns a token that represents the user's prospective purchase via /// user choice alternative billing. - @JsonKey(defaultValue: '') final String externalTransactionToken; /// Returns a list of [UserChoiceDetailsProductWrapper] to be purchased in /// the user choice alternative billing flow. - @JsonKey(defaultValue: []) final List products; } @@ -78,8 +58,6 @@ class UserChoiceDetailsWrapper { // // See https://docs.flutter.dev/data-and-backend/serialization/json#generating-code-for-nested-classes // for explination for why this uses explicitToJson. -@JsonSerializable(createToJson: true, explicitToJson: true) -@ProductTypeConverter() @immutable class UserChoiceDetailsProductWrapper { /// Creates a [UserChoiceDetailsProductWrapper] with the given record details. @@ -89,22 +67,10 @@ class UserChoiceDetailsProductWrapper { required this.productType, }); - /// Factory for creating a [UserChoiceDetailsProductWrapper] from a [Map] with the record details. - @Deprecated('JSON serialization is not intended for public use, and will ' - 'be removed in a future version.') - factory UserChoiceDetailsProductWrapper.fromJson(Map map) => - _$UserChoiceDetailsProductWrapperFromJson(map); - - /// Creates a JSON representation of this product. - Map toJson() => - _$UserChoiceDetailsProductWrapperToJson(this); - /// Returns the id of the product being purchased. - @JsonKey(defaultValue: '') final String id; /// Returns the offer token that was passed in launchBillingFlow to purchase the product. - @JsonKey(defaultValue: '') final String offerToken; /// Returns the [ProductType] of the product being purchased. diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/user_choice_details_wrapper.g.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/user_choice_details_wrapper.g.dart deleted file mode 100644 index 669d263588d..00000000000 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/user_choice_details_wrapper.g.dart +++ /dev/null @@ -1,45 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'user_choice_details_wrapper.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -UserChoiceDetailsWrapper _$UserChoiceDetailsWrapperFromJson(Map json) => - UserChoiceDetailsWrapper( - originalExternalTransactionId: - json['originalExternalTransactionId'] as String? ?? '', - externalTransactionToken: - json['externalTransactionToken'] as String? ?? '', - products: (json['products'] as List?) - ?.map((e) => UserChoiceDetailsProductWrapper.fromJson( - Map.from(e as Map))) - .toList() ?? - [], - ); - -Map _$UserChoiceDetailsWrapperToJson( - UserChoiceDetailsWrapper instance) => - { - 'originalExternalTransactionId': instance.originalExternalTransactionId, - 'externalTransactionToken': instance.externalTransactionToken, - 'products': instance.products.map((e) => e.toJson()).toList(), - }; - -UserChoiceDetailsProductWrapper _$UserChoiceDetailsProductWrapperFromJson( - Map json) => - UserChoiceDetailsProductWrapper( - id: json['id'] as String? ?? '', - offerToken: json['offerToken'] as String? ?? '', - productType: - const ProductTypeConverter().fromJson(json['productType'] as String?), - ); - -Map _$UserChoiceDetailsProductWrapperToJson( - UserChoiceDetailsProductWrapper instance) => - { - 'id': instance.id, - 'offerToken': instance.offerToken, - 'productType': const ProductTypeConverter().toJson(instance.productType), - }; 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 333ccc83bc2..f39d5817367 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 @@ -162,7 +162,6 @@ class InAppPurchaseAndroidPlatform extends InAppPurchasePlatform { oldProduct: changeSubscriptionParam?.oldPurchaseDetails.productID, purchaseToken: changeSubscriptionParam ?.oldPurchaseDetails.verificationData.serverVerificationData, - prorationMode: changeSubscriptionParam?.prorationMode, replacementMode: changeSubscriptionParam?.replacementMode), ); return billingResultWrapper.responseCode == BillingResponse.ok; 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 aa2f473d263..cc94285eb98 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 @@ -31,32 +31,6 @@ class InAppPurchaseAndroidPlatformAddition late final Stream userChoiceDetailsStream = _userChoiceDetailsStreamController.stream; - /// Whether pending purchase is enabled. - /// - /// **Deprecation warning:** it is no longer required to call - /// [enablePendingPurchases] when initializing your application. From now on - /// this is handled internally and the [enablePendingPurchase] property will - /// always return `true`. - /// - /// See also [enablePendingPurchases] for more on pending purchases. - @Deprecated( - 'The requirement to call `enablePendingPurchases()` has become obsolete ' - "since Google Play no longer accepts app submissions that don't support " - 'pending purchases.') - static bool get enablePendingPurchase => true; - - /// Enable the [InAppPurchaseConnection] to handle pending purchases. - /// - /// **Deprecation warning:** it is no longer required to call - /// [enablePendingPurchases] when initializing your application. - @Deprecated( - 'The requirement to call `enablePendingPurchases()` has become obsolete ' - "since Google Play no longer accepts app submissions that don't support " - 'pending purchases.') - static void enablePendingPurchases() { - // No-op, until it is time to completely remove this method from the API. - } - final BillingClientManager _billingClientManager; /// Mark that the user has consumed a product. diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/messages.g.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/messages.g.dart index 1e6cca2ccbd..72e3ab5c16c 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/messages.g.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/messages.g.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 (v22.4.2), do not edit directly. +// Autogenerated from Pigeon (v22.6.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, no_leading_underscores_for_local_identifiers @@ -29,6 +29,32 @@ List wrapResponse( return [error.code, error.message, error.details]; } +/// Pigeon version of Java BillingClient.BillingResponseCode. +enum PlatformBillingResponse { + serviceTimeout, + featureNotSupported, + serviceDisconnected, + ok, + userCanceled, + serviceUnavailable, + billingUnavailable, + itemUnavailable, + developerError, + error, + itemAlreadyOwned, + itemNotOwned, + networkError, +} + +enum PlatformReplacementMode { + unknownReplacementMode, + withTimeProration, + chargeProratedPrice, + withoutProration, + deferred, + chargeFullPrice, +} + /// Pigeon version of Java BillingClient.ProductType. enum PlatformProductType { inapp, @@ -49,6 +75,17 @@ enum PlatformBillingChoiceMode { userChoiceBilling, } +enum PlatformBillingClientFeature { + alternativeBillingOnly, + billingConfig, + externalOffer, + inAppMessaging, + priceChangeConfirmation, + productDetails, + subscriptions, + subscriptionsUpdate, +} + /// Pigeon version of Java Purchase.PurchaseState. enum PlatformPurchaseState { unspecified, @@ -124,7 +161,7 @@ class PlatformBillingResult { required this.debugMessage, }); - int responseCode; + PlatformBillingResponse responseCode; String debugMessage; @@ -138,7 +175,7 @@ class PlatformBillingResult { static PlatformBillingResult decode(Object result) { result as List; return PlatformBillingResult( - responseCode: result[0]! as int, + responseCode: result[0]! as PlatformBillingResponse, debugMessage: result[1]! as String, ); } @@ -321,7 +358,6 @@ class PlatformBillingConfigResponse { class PlatformBillingFlowParams { PlatformBillingFlowParams({ required this.product, - required this.prorationMode, required this.replacementMode, this.offerToken, this.accountId, @@ -332,9 +368,7 @@ class PlatformBillingFlowParams { String product; - int prorationMode; - - int replacementMode; + PlatformReplacementMode replacementMode; String? offerToken; @@ -349,7 +383,6 @@ class PlatformBillingFlowParams { Object encode() { return [ product, - prorationMode, replacementMode, offerToken, accountId, @@ -363,13 +396,12 @@ class PlatformBillingFlowParams { result as List; return PlatformBillingFlowParams( product: result[0]! as String, - prorationMode: result[1]! as int, - replacementMode: result[2]! as int, - offerToken: result[3] as String?, - accountId: result[4] as String?, - obfuscatedProfileId: result[5] as String?, - oldProduct: result[6] as String?, - purchaseToken: result[7] as String?, + replacementMode: result[1]! as PlatformReplacementMode, + offerToken: result[2] as String?, + accountId: result[3] as String?, + obfuscatedProfileId: result[4] as String?, + oldProduct: result[5] as String?, + purchaseToken: result[6] as String?, ); } } @@ -439,6 +471,7 @@ class PlatformPurchase { required this.quantity, required this.purchaseState, this.accountIdentifiers, + this.pendingPurchaseUpdate, }); String? orderId; @@ -467,6 +500,8 @@ class PlatformPurchase { PlatformAccountIdentifiers? accountIdentifiers; + PlatformPendingPurchaseUpdate? pendingPurchaseUpdate; + Object encode() { return [ orderId, @@ -482,6 +517,7 @@ class PlatformPurchase { quantity, purchaseState, accountIdentifiers, + pendingPurchaseUpdate, ]; } @@ -501,6 +537,36 @@ class PlatformPurchase { quantity: result[10]! as int, purchaseState: result[11]! as PlatformPurchaseState, accountIdentifiers: result[12] as PlatformAccountIdentifiers?, + pendingPurchaseUpdate: result[13] as PlatformPendingPurchaseUpdate?, + ); + } +} + +/// Pigeon version of Java Purchase. +/// +/// See also PendingPurchaseUpdateWrapper on the Dart side. +class PlatformPendingPurchaseUpdate { + PlatformPendingPurchaseUpdate({ + required this.products, + required this.purchaseToken, + }); + + List products; + + String purchaseToken; + + Object encode() { + return [ + products, + purchaseToken, + ]; + } + + static PlatformPendingPurchaseUpdate decode(Object result) { + result as List; + return PlatformPendingPurchaseUpdate( + products: (result[0] as List?)!.cast(), + purchaseToken: result[1]! as String, ); } } @@ -624,6 +690,7 @@ class PlatformSubscriptionOfferDetails { required this.offerToken, required this.offerTags, required this.pricingPhases, + this.installmentPlanDetails, }); String basePlanId; @@ -636,6 +703,8 @@ class PlatformSubscriptionOfferDetails { List pricingPhases; + PlatformInstallmentPlanDetails? installmentPlanDetails; + Object encode() { return [ basePlanId, @@ -643,6 +712,7 @@ class PlatformSubscriptionOfferDetails { offerToken, offerTags, pricingPhases, + installmentPlanDetails, ]; } @@ -655,6 +725,7 @@ class PlatformSubscriptionOfferDetails { offerTags: (result[3] as List?)!.cast(), pricingPhases: (result[4] as List?)!.cast(), + installmentPlanDetails: result[5] as PlatformInstallmentPlanDetails?, ); } } @@ -724,6 +795,56 @@ class PlatformUserChoiceProduct { } } +/// Pigeon version of ProductDetails.InstallmentPlanDetails. +/// https://developer.android.com/reference/com/android/billingclient/api/PendingPurchasesParams.Builder#enableOneTimeProducts() +class PlatformInstallmentPlanDetails { + PlatformInstallmentPlanDetails({ + required this.commitmentPaymentsCount, + required this.subsequentCommitmentPaymentsCount, + }); + + int commitmentPaymentsCount; + + int subsequentCommitmentPaymentsCount; + + Object encode() { + return [ + commitmentPaymentsCount, + subsequentCommitmentPaymentsCount, + ]; + } + + static PlatformInstallmentPlanDetails decode(Object result) { + result as List; + return PlatformInstallmentPlanDetails( + commitmentPaymentsCount: result[0]! as int, + subsequentCommitmentPaymentsCount: result[1]! as int, + ); + } +} + +/// Pigeon version of Java PendingPurchasesParams. +class PlatformPendingPurchasesParams { + PlatformPendingPurchasesParams({ + required this.enablePrepaidPlans, + }); + + bool enablePrepaidPlans; + + Object encode() { + return [ + enablePrepaidPlans, + ]; + } + + static PlatformPendingPurchasesParams decode(Object result) { + result as List; + return PlatformPendingPurchasesParams( + enablePrepaidPlans: result[0]! as bool, + ); + } +} + class _PigeonCodec extends StandardMessageCodec { const _PigeonCodec(); @override @@ -731,69 +852,87 @@ class _PigeonCodec extends StandardMessageCodec { if (value is int) { buffer.putUint8(4); buffer.putInt64(value); - } else if (value is PlatformProductType) { + } else if (value is PlatformBillingResponse) { buffer.putUint8(129); writeValue(buffer, value.index); - } else if (value is PlatformBillingChoiceMode) { + } else if (value is PlatformReplacementMode) { buffer.putUint8(130); writeValue(buffer, value.index); - } else if (value is PlatformPurchaseState) { + } else if (value is PlatformProductType) { buffer.putUint8(131); writeValue(buffer, value.index); - } else if (value is PlatformRecurrenceMode) { + } else if (value is PlatformBillingChoiceMode) { buffer.putUint8(132); writeValue(buffer, value.index); - } else if (value is PlatformQueryProduct) { + } else if (value is PlatformBillingClientFeature) { buffer.putUint8(133); + writeValue(buffer, value.index); + } else if (value is PlatformPurchaseState) { + buffer.putUint8(134); + writeValue(buffer, value.index); + } else if (value is PlatformRecurrenceMode) { + buffer.putUint8(135); + writeValue(buffer, value.index); + } else if (value is PlatformQueryProduct) { + buffer.putUint8(136); writeValue(buffer, value.encode()); } else if (value is PlatformAccountIdentifiers) { - buffer.putUint8(134); + buffer.putUint8(137); writeValue(buffer, value.encode()); } else if (value is PlatformBillingResult) { - buffer.putUint8(135); + buffer.putUint8(138); writeValue(buffer, value.encode()); } else if (value is PlatformOneTimePurchaseOfferDetails) { - buffer.putUint8(136); + buffer.putUint8(139); writeValue(buffer, value.encode()); } else if (value is PlatformProductDetails) { - buffer.putUint8(137); + buffer.putUint8(140); writeValue(buffer, value.encode()); } else if (value is PlatformProductDetailsResponse) { - buffer.putUint8(138); + buffer.putUint8(141); writeValue(buffer, value.encode()); } else if (value is PlatformAlternativeBillingOnlyReportingDetailsResponse) { - buffer.putUint8(139); + buffer.putUint8(142); writeValue(buffer, value.encode()); } else if (value is PlatformBillingConfigResponse) { - buffer.putUint8(140); + buffer.putUint8(143); writeValue(buffer, value.encode()); } else if (value is PlatformBillingFlowParams) { - buffer.putUint8(141); + buffer.putUint8(144); writeValue(buffer, value.encode()); } else if (value is PlatformPricingPhase) { - buffer.putUint8(142); + buffer.putUint8(145); writeValue(buffer, value.encode()); } else if (value is PlatformPurchase) { - buffer.putUint8(143); + buffer.putUint8(146); + writeValue(buffer, value.encode()); + } else if (value is PlatformPendingPurchaseUpdate) { + buffer.putUint8(147); writeValue(buffer, value.encode()); } else if (value is PlatformPurchaseHistoryRecord) { - buffer.putUint8(144); + buffer.putUint8(148); writeValue(buffer, value.encode()); } else if (value is PlatformPurchaseHistoryResponse) { - buffer.putUint8(145); + buffer.putUint8(149); writeValue(buffer, value.encode()); } else if (value is PlatformPurchasesResponse) { - buffer.putUint8(146); + buffer.putUint8(150); writeValue(buffer, value.encode()); } else if (value is PlatformSubscriptionOfferDetails) { - buffer.putUint8(147); + buffer.putUint8(151); writeValue(buffer, value.encode()); } else if (value is PlatformUserChoiceDetails) { - buffer.putUint8(148); + buffer.putUint8(152); writeValue(buffer, value.encode()); } else if (value is PlatformUserChoiceProduct) { - buffer.putUint8(149); + buffer.putUint8(153); + writeValue(buffer, value.encode()); + } else if (value is PlatformInstallmentPlanDetails) { + buffer.putUint8(154); + writeValue(buffer, value.encode()); + } else if (value is PlatformPendingPurchasesParams) { + buffer.putUint8(155); writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); @@ -805,51 +944,68 @@ class _PigeonCodec extends StandardMessageCodec { switch (type) { case 129: final int? value = readValue(buffer) as int?; - return value == null ? null : PlatformProductType.values[value]; + return value == null ? null : PlatformBillingResponse.values[value]; case 130: final int? value = readValue(buffer) as int?; - return value == null ? null : PlatformBillingChoiceMode.values[value]; + return value == null ? null : PlatformReplacementMode.values[value]; case 131: final int? value = readValue(buffer) as int?; - return value == null ? null : PlatformPurchaseState.values[value]; + return value == null ? null : PlatformProductType.values[value]; case 132: final int? value = readValue(buffer) as int?; - return value == null ? null : PlatformRecurrenceMode.values[value]; + return value == null ? null : PlatformBillingChoiceMode.values[value]; case 133: - return PlatformQueryProduct.decode(readValue(buffer)!); + final int? value = readValue(buffer) as int?; + return value == null + ? null + : PlatformBillingClientFeature.values[value]; case 134: - return PlatformAccountIdentifiers.decode(readValue(buffer)!); + final int? value = readValue(buffer) as int?; + return value == null ? null : PlatformPurchaseState.values[value]; case 135: - return PlatformBillingResult.decode(readValue(buffer)!); + final int? value = readValue(buffer) as int?; + return value == null ? null : PlatformRecurrenceMode.values[value]; case 136: - return PlatformOneTimePurchaseOfferDetails.decode(readValue(buffer)!); + return PlatformQueryProduct.decode(readValue(buffer)!); case 137: - return PlatformProductDetails.decode(readValue(buffer)!); + return PlatformAccountIdentifiers.decode(readValue(buffer)!); case 138: - return PlatformProductDetailsResponse.decode(readValue(buffer)!); + return PlatformBillingResult.decode(readValue(buffer)!); case 139: + return PlatformOneTimePurchaseOfferDetails.decode(readValue(buffer)!); + case 140: + return PlatformProductDetails.decode(readValue(buffer)!); + case 141: + return PlatformProductDetailsResponse.decode(readValue(buffer)!); + case 142: return PlatformAlternativeBillingOnlyReportingDetailsResponse.decode( readValue(buffer)!); - case 140: + case 143: return PlatformBillingConfigResponse.decode(readValue(buffer)!); - case 141: + case 144: return PlatformBillingFlowParams.decode(readValue(buffer)!); - case 142: + case 145: return PlatformPricingPhase.decode(readValue(buffer)!); - case 143: + case 146: return PlatformPurchase.decode(readValue(buffer)!); - case 144: + case 147: + return PlatformPendingPurchaseUpdate.decode(readValue(buffer)!); + case 148: return PlatformPurchaseHistoryRecord.decode(readValue(buffer)!); - case 145: + case 149: return PlatformPurchaseHistoryResponse.decode(readValue(buffer)!); - case 146: + case 150: return PlatformPurchasesResponse.decode(readValue(buffer)!); - case 147: + case 151: return PlatformSubscriptionOfferDetails.decode(readValue(buffer)!); - case 148: + case 152: return PlatformUserChoiceDetails.decode(readValue(buffer)!); - case 149: + case 153: return PlatformUserChoiceProduct.decode(readValue(buffer)!); + case 154: + return PlatformInstallmentPlanDetails.decode(readValue(buffer)!); + case 155: + return PlatformPendingPurchasesParams.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); } @@ -903,7 +1059,9 @@ class InAppPurchaseApi { /// Wraps BillingClient#startConnection(BillingClientStateListener). Future startConnection( - int callbackHandle, PlatformBillingChoiceMode billingMode) async { + int callbackHandle, + PlatformBillingChoiceMode billingMode, + PlatformPendingPurchasesParams pendingPurchasesParams) async { final String pigeonVar_channelName = 'dev.flutter.pigeon.in_app_purchase_android.InAppPurchaseApi.startConnection$pigeonVar_messageChannelSuffix'; final BasicMessageChannel pigeonVar_channel = @@ -912,8 +1070,9 @@ class InAppPurchaseApi { pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final List? pigeonVar_replyList = await pigeonVar_channel - .send([callbackHandle, billingMode]) as List?; + final List? pigeonVar_replyList = await pigeonVar_channel.send( + [callbackHandle, billingMode, pendingPurchasesParams]) + as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -1173,7 +1332,7 @@ class InAppPurchaseApi { } /// Wraps BillingClient#isFeatureSupported(String). - Future isFeatureSupported(String feature) async { + Future isFeatureSupported(PlatformBillingClientFeature feature) async { final String pigeonVar_channelName = 'dev.flutter.pigeon.in_app_purchase_android.InAppPurchaseApi.isFeatureSupported$pigeonVar_messageChannelSuffix'; final BasicMessageChannel pigeonVar_channel = diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/pigeon_converters.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/pigeon_converters.dart index bc93f614704..78393f94c20 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/pigeon_converters.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/pigeon_converters.dart @@ -2,8 +2,11 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart'; + import '../billing_client_wrappers.dart'; import 'billing_client_wrappers/billing_config_wrapper.dart'; +import 'billing_client_wrappers/pending_purchases_params_wrapper.dart'; import 'messages.g.dart'; /// Converts a [BillingChoiceMode] to the Pigeon equivalent. @@ -21,8 +24,7 @@ PlatformBillingChoiceMode platformBillingChoiceMode(BillingChoiceMode mode) { /// Creates a [BillingResultWrapper] from the Pigeon equivalent. BillingResultWrapper resultWrapperFromPlatform(PlatformBillingResult result) { return BillingResultWrapper( - responseCode: - const BillingResponseConverter().fromJson(result.responseCode), + responseCode: billingResponseFromPlatform(result.responseCode), debugMessage: result.debugMessage); } @@ -100,8 +102,7 @@ PurchasesResultWrapper purchasesResultWrapperFromPlatform( purchasesList: response.purchases.map(purchaseWrapperFromPlatform).toList(), responseCode: forceOkResponseCode ? BillingResponse.ok - : const BillingResponseConverter() - .fromJson(response.billingResult.responseCode), + : billingResponseFromPlatform(response.billingResult.responseCode), ); } @@ -111,8 +112,9 @@ AlternativeBillingOnlyReportingDetailsWrapper alternativeBillingOnlyReportingDetailsWrapperFromPlatform( PlatformAlternativeBillingOnlyReportingDetailsResponse response) { return AlternativeBillingOnlyReportingDetailsWrapper( - responseCode: const BillingResponseConverter() - .fromJson(response.billingResult.responseCode), + responseCode: billingResponseFromPlatform( + response.billingResult.responseCode, + ), debugMessage: response.billingResult.debugMessage, externalTransactionToken: response.externalTransactionToken, ); @@ -122,8 +124,9 @@ AlternativeBillingOnlyReportingDetailsWrapper BillingConfigWrapper billingConfigWrapperFromPlatform( PlatformBillingConfigResponse response) { return BillingConfigWrapper( - responseCode: const BillingResponseConverter() - .fromJson(response.billingResult.responseCode), + responseCode: billingResponseFromPlatform( + response.billingResult.responseCode, + ), debugMessage: response.billingResult.debugMessage, countryCode: response.countryCode, ); @@ -182,9 +185,23 @@ PurchaseWrapper purchaseWrapperFromPlatform(PlatformPurchase purchase) { developerPayload: purchase.developerPayload, obfuscatedAccountId: purchase.accountIdentifiers?.obfuscatedAccountId, obfuscatedProfileId: purchase.accountIdentifiers?.obfuscatedProfileId, + pendingPurchaseUpdate: + pendingPurchaseUpdateFromPlatform(purchase.pendingPurchaseUpdate), ); } +/// Creates a [PendingPurchaseUpdateWrapper] from the Pigeon equivalent. +PendingPurchaseUpdateWrapper? pendingPurchaseUpdateFromPlatform( + PlatformPendingPurchaseUpdate? pendingPurchaseUpdate) { + if (pendingPurchaseUpdate == null) { + return null; + } + + return PendingPurchaseUpdateWrapper( + purchaseToken: pendingPurchaseUpdate.purchaseToken, + products: pendingPurchaseUpdate.products); +} + /// Creates a [PurchaseStateWrapper] from the Pigeon equivalent. PurchaseStateWrapper purchaseStateWrapperFromPlatform( PlatformPurchaseState state) { @@ -195,6 +212,15 @@ PurchaseStateWrapper purchaseStateWrapperFromPlatform( }; } +/// Converts [PurchaseStateWrapper] to [PurchaseStatus]. +PurchaseStatus purchaseStatusFromWrapper(PurchaseStateWrapper purchaseState) { + return switch (purchaseState) { + PurchaseStateWrapper.unspecified_state => PurchaseStatus.error, + PurchaseStateWrapper.purchased => PurchaseStatus.purchased, + PurchaseStateWrapper.pending => PurchaseStatus.pending, + }; +} + /// Creates a [RecurrenceMode] from the Pigeon equivalent. RecurrenceMode recurrenceModeFromPlatform(PlatformRecurrenceMode mode) { return switch (mode) { @@ -215,6 +241,8 @@ SubscriptionOfferDetailsWrapper subscriptionOfferDetailsWrapperFromPlatform( offerIdToken: offer.offerToken, pricingPhases: offer.pricingPhases.map(pricingPhaseWrapperFromPlatform).toList(), + installmentPlanDetails: + installmentPlanDetailsFromPlatform(offer.installmentPlanDetails), ); } @@ -238,3 +266,115 @@ UserChoiceDetailsProductWrapper userChoiceDetailsProductFromPlatform( productType: productTypeFromPlatform(product.type), ); } + +/// Creates a [InstallmentPlanDetailsWrapper] from the Pigeon equivalent. +InstallmentPlanDetailsWrapper? installmentPlanDetailsFromPlatform( + PlatformInstallmentPlanDetails? details) { + if (details == null) { + return null; + } + + return InstallmentPlanDetailsWrapper( + commitmentPaymentsCount: details.commitmentPaymentsCount, + subsequentCommitmentPaymentsCount: + details.subsequentCommitmentPaymentsCount, + ); +} + +/// Converts a [PendingPurchasesParamsWrapper] to its Pigeon equivalent. +PlatformPendingPurchasesParams pendingPurchasesParamsFromWrapper( + PendingPurchasesParamsWrapper params) { + return PlatformPendingPurchasesParams( + enablePrepaidPlans: params.enablePrepaidPlans, + ); +} + +/// Converts [PlatformBillingResponse] to its public API enum equivalent. +BillingResponse billingResponseFromPlatform( + PlatformBillingResponse responseCode) { + return switch (responseCode) { + PlatformBillingResponse.serviceTimeout => BillingResponse.serviceTimeout, + PlatformBillingResponse.featureNotSupported => + BillingResponse.featureNotSupported, + PlatformBillingResponse.serviceDisconnected => + BillingResponse.serviceDisconnected, + PlatformBillingResponse.ok => BillingResponse.ok, + PlatformBillingResponse.userCanceled => BillingResponse.userCanceled, + PlatformBillingResponse.serviceUnavailable => + BillingResponse.serviceUnavailable, + PlatformBillingResponse.billingUnavailable => + BillingResponse.billingUnavailable, + PlatformBillingResponse.itemUnavailable => BillingResponse.itemUnavailable, + PlatformBillingResponse.developerError => BillingResponse.developerError, + PlatformBillingResponse.error => BillingResponse.error, + PlatformBillingResponse.itemAlreadyOwned => + BillingResponse.itemAlreadyOwned, + PlatformBillingResponse.itemNotOwned => BillingResponse.itemNotOwned, + PlatformBillingResponse.networkError => BillingResponse.networkError, + }; +} + +/// Converts a [BillingResponse] to its Pigeon equivalent. +PlatformBillingResponse billingResponseFromWrapper( + BillingResponse responseCode) { + return switch (responseCode) { + BillingResponse.serviceTimeout => PlatformBillingResponse.serviceTimeout, + BillingResponse.featureNotSupported => + PlatformBillingResponse.featureNotSupported, + BillingResponse.serviceDisconnected => + PlatformBillingResponse.serviceDisconnected, + BillingResponse.ok => PlatformBillingResponse.ok, + BillingResponse.userCanceled => PlatformBillingResponse.userCanceled, + BillingResponse.serviceUnavailable => + PlatformBillingResponse.serviceUnavailable, + BillingResponse.billingUnavailable => + PlatformBillingResponse.billingUnavailable, + BillingResponse.itemUnavailable => PlatformBillingResponse.itemUnavailable, + BillingResponse.developerError => PlatformBillingResponse.developerError, + BillingResponse.error => PlatformBillingResponse.error, + BillingResponse.itemAlreadyOwned => + PlatformBillingResponse.itemAlreadyOwned, + BillingResponse.itemNotOwned => PlatformBillingResponse.itemNotOwned, + BillingResponse.networkError => PlatformBillingResponse.networkError, + }; +} + +/// Converts [ReplacementMode] enum to its Pigeon equivalent. +PlatformReplacementMode replacementModeFromWrapper( + ReplacementMode replacementMode) { + return switch (replacementMode) { + ReplacementMode.unknownReplacementMode => + PlatformReplacementMode.unknownReplacementMode, + ReplacementMode.withTimeProration => + PlatformReplacementMode.withTimeProration, + ReplacementMode.chargeProratedPrice => + PlatformReplacementMode.chargeProratedPrice, + ReplacementMode.withoutProration => + PlatformReplacementMode.withoutProration, + ReplacementMode.deferred => PlatformReplacementMode.deferred, + ReplacementMode.chargeFullPrice => PlatformReplacementMode.chargeFullPrice, + }; +} + +/// Converts [BillingClientFeature] enum to its Pigeon equivalent. +PlatformBillingClientFeature billingClientFeatureFromWrapper( + BillingClientFeature feature) { + return switch (feature) { + BillingClientFeature.alternativeBillingOnly => + PlatformBillingClientFeature.alternativeBillingOnly, + BillingClientFeature.priceChangeConfirmation => + PlatformBillingClientFeature.priceChangeConfirmation, + BillingClientFeature.productDetails => + PlatformBillingClientFeature.productDetails, + BillingClientFeature.subscriptions => + PlatformBillingClientFeature.subscriptions, + BillingClientFeature.subscriptionsUpdate => + PlatformBillingClientFeature.subscriptionsUpdate, + BillingClientFeature.billingConfig => + PlatformBillingClientFeature.billingConfig, + BillingClientFeature.externalOffer => + PlatformBillingClientFeature.externalOffer, + BillingClientFeature.inAppMessaging => + PlatformBillingClientFeature.inAppMessaging, + }; +} diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/types/change_subscription_param.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/types/change_subscription_param.dart index f76fc3e9005..25907402469 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/types/change_subscription_param.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/types/change_subscription_param.dart @@ -10,7 +10,6 @@ class ChangeSubscriptionParam { /// Creates a new change subscription param object with given data ChangeSubscriptionParam({ required this.oldPurchaseDetails, - @Deprecated('Use replacementMode instead') this.prorationMode, this.replacementMode, }); @@ -18,13 +17,6 @@ class ChangeSubscriptionParam { /// upgrade/downgrade from. final GooglePlayPurchaseDetails oldPurchaseDetails; - /// The proration mode. - /// - /// This is an optional parameter that indicates how to handle the existing - /// subscription when the new subscription comes into effect. - @Deprecated('Use replacementMode instead') - final ProrationMode? prorationMode; - /// The replacement mode. /// /// This is an optional parameter that indicates how to handle the existing diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/types/google_play_purchase_details.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/types/google_play_purchase_details.dart index f2596d61326..1dbe4305fa5 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/types/google_play_purchase_details.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/types/google_play_purchase_details.dart @@ -6,6 +6,7 @@ import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_inte import '../../billing_client_wrappers.dart'; import '../in_app_purchase_android_platform.dart'; +import '../pigeon_converters.dart'; /// The class represents the information of a purchase made using Google Play. class GooglePlayPurchaseDetails extends PurchaseDetails { @@ -38,8 +39,7 @@ class GooglePlayPurchaseDetails extends PurchaseDetails { source: kIAPSource), transactionDate: purchase.purchaseTime.toString(), billingClientPurchase: purchase, - status: const PurchaseStateConverter() - .toPurchaseStatus(purchase.purchaseState), + status: purchaseStatusFromWrapper(purchase.purchaseState), ); if (purchaseDetails.status == PurchaseStatus.error) { diff --git a/packages/in_app_purchase/in_app_purchase_android/pigeons/messages.dart b/packages/in_app_purchase/in_app_purchase_android/pigeons/messages.dart index 3c15cc4750a..1e15f2ad8e3 100644 --- a/packages/in_app_purchase/in_app_purchase_android/pigeons/messages.dart +++ b/packages/in_app_purchase/in_app_purchase_android/pigeons/messages.dart @@ -35,10 +35,27 @@ class PlatformAccountIdentifiers { class PlatformBillingResult { PlatformBillingResult( {required this.responseCode, required this.debugMessage}); - final int responseCode; + final PlatformBillingResponse responseCode; final String debugMessage; } +/// Pigeon version of Java BillingClient.BillingResponseCode. +enum PlatformBillingResponse { + serviceTimeout, + featureNotSupported, + serviceDisconnected, + ok, + userCanceled, + serviceUnavailable, + billingUnavailable, + itemUnavailable, + developerError, + error, + itemAlreadyOwned, + itemNotOwned, + networkError, +} + /// Pigeon version of Java ProductDetails.OneTimePurchaseOfferDetails. class PlatformOneTimePurchaseOfferDetails { PlatformOneTimePurchaseOfferDetails({ @@ -110,7 +127,6 @@ class PlatformBillingConfigResponse { class PlatformBillingFlowParams { PlatformBillingFlowParams({ required this.product, - required this.prorationMode, required this.replacementMode, required this.offerToken, required this.accountId, @@ -120,11 +136,7 @@ class PlatformBillingFlowParams { }); final String product; - // Ideally this would be replaced with an enum on the dart side that maps - // to constants on the Java side, but it's deprecated anyway so that will be - // resolved during the update to the new API. - final int prorationMode; - final int replacementMode; + final PlatformReplacementMode replacementMode; final String? offerToken; final String? accountId; final String? obfuscatedProfileId; @@ -132,6 +144,15 @@ class PlatformBillingFlowParams { final String? purchaseToken; } +enum PlatformReplacementMode { + unknownReplacementMode, + withTimeProration, + chargeProratedPrice, + withoutProration, + deferred, + chargeFullPrice, +} + /// Pigeon version of Java ProductDetails.PricingPhase. class PlatformPricingPhase { PlatformPricingPhase({ @@ -169,6 +190,7 @@ class PlatformPurchase { required this.quantity, required this.purchaseState, required this.accountIdentifiers, + required this.pendingPurchaseUpdate, }); final String? orderId; @@ -184,6 +206,20 @@ class PlatformPurchase { final int quantity; final PlatformPurchaseState purchaseState; final PlatformAccountIdentifiers? accountIdentifiers; + final PlatformPendingPurchaseUpdate? pendingPurchaseUpdate; +} + +/// Pigeon version of Java Purchase. +/// +/// See also PendingPurchaseUpdateWrapper on the Dart side. +class PlatformPendingPurchaseUpdate { + PlatformPendingPurchaseUpdate({ + required this.products, + required this.purchaseToken, + }); + + final List products; + final String purchaseToken; } /// Pigeon version of PurchaseHistoryRecord. @@ -241,6 +277,7 @@ class PlatformSubscriptionOfferDetails { required this.offerToken, required this.offerTags, required this.pricingPhases, + required this.installmentPlanDetails, }); final String basePlanId; @@ -252,6 +289,7 @@ class PlatformSubscriptionOfferDetails { // internal API, we can always add that indirection later if we need it, // so for now this bypasses that unnecessary wrapper. final List pricingPhases; + final PlatformInstallmentPlanDetails? installmentPlanDetails; } /// Pigeon version of UserChoiceDetailsWrapper and Java UserChoiceDetails. @@ -280,6 +318,27 @@ class PlatformUserChoiceProduct { final PlatformProductType type; } +/// Pigeon version of ProductDetails.InstallmentPlanDetails. +/// https://developer.android.com/reference/com/android/billingclient/api/PendingPurchasesParams.Builder#enableOneTimeProducts() +class PlatformInstallmentPlanDetails { + PlatformInstallmentPlanDetails({ + required this.commitmentPaymentsCount, + required this.subsequentCommitmentPaymentsCount, + }); + + final int commitmentPaymentsCount; + final int subsequentCommitmentPaymentsCount; +} + +/// Pigeon version of Java PendingPurchasesParams. +class PlatformPendingPurchasesParams { + PlatformPendingPurchasesParams({ + required this.enablePrepaidPlans, + }); + + final bool enablePrepaidPlans; +} + /// Pigeon version of Java BillingClient.ProductType. enum PlatformProductType { inapp, @@ -300,6 +359,18 @@ enum PlatformBillingChoiceMode { userChoiceBilling, } +/// Pigeon version of Java BillingClient.FeatureType. +enum PlatformBillingClientFeature { + alternativeBillingOnly, + billingConfig, + externalOffer, + inAppMessaging, + priceChangeConfirmation, + productDetails, + subscriptions, + subscriptionsUpdate, +} + /// Pigeon version of Java Purchase.PurchaseState. enum PlatformPurchaseState { unspecified, @@ -322,7 +393,9 @@ abstract class InAppPurchaseApi { /// Wraps BillingClient#startConnection(BillingClientStateListener). @async PlatformBillingResult startConnection( - int callbackHandle, PlatformBillingChoiceMode billingMode); + int callbackHandle, + PlatformBillingChoiceMode billingMode, + PlatformPendingPurchasesParams pendingPurchasesParams); /// Wraps BillingClient#endConnection(BillingClientStateListener). void endConnection(); @@ -358,10 +431,7 @@ abstract class InAppPurchaseApi { List products); /// Wraps BillingClient#isFeatureSupported(String). - // TODO(stuartmorgan): Consider making this take a enum, and converting the - // enum value to string constants on the native side, so that magic strings - // from the Play Billing API aren't duplicated in Dart code. - bool isFeatureSupported(String feature); + bool isFeatureSupported(PlatformBillingClientFeature feature); /// Wraps BillingClient#isAlternativeBillingOnlyAvailableAsync(). @async 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 0932606e7ae..bef60d7c6fd 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/packages/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.3.6+13 +version: 0.4.0 environment: sdk: ^3.5.0 @@ -21,13 +21,11 @@ dependencies: flutter: sdk: flutter in_app_purchase_platform_interface: ^1.4.0 - json_annotation: ^4.8.0 dev_dependencies: build_runner: ^2.0.0 flutter_test: sdk: flutter - json_serializable: ^6.3.1 mockito: ^5.4.4 pigeon: ^22.4.2 test: ^1.16.0 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 1c72d02f99e..59eb1d01f5c 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 @@ -7,6 +7,7 @@ import 'dart:async'; 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/billing_client_wrappers/pending_purchases_params_wrapper.dart'; import 'package:in_app_purchase_android/src/messages.g.dart'; import 'package:mockito/mockito.dart'; @@ -21,8 +22,9 @@ void main() { setUp(() { WidgetsFlutterBinding.ensureInitialized(); mockApi = MockInAppPurchaseApi(); - when(mockApi.startConnection(any, any)).thenAnswer( - (_) async => PlatformBillingResult(responseCode: 0, debugMessage: '')); + when(mockApi.startConnection(any, any, any)).thenAnswer((_) async => + PlatformBillingResult( + responseCode: PlatformBillingResponse.ok, debugMessage: '')); manager = BillingClientManager( billingClientFactory: (PurchasesUpdatedListener listener, UserSelectedAlternativeBillingListener? @@ -32,14 +34,15 @@ void main() { group('BillingClientWrapper', () { test('connects on initialization', () { - verify(mockApi.startConnection(any, any)).called(1); + verify(mockApi.startConnection(any, any, any)).called(1); }); test('waits for connection before executing the operations', () async { final Completer connectedCompleter = Completer(); - when(mockApi.startConnection(any, any)).thenAnswer((_) async { + when(mockApi.startConnection(any, any, any)).thenAnswer((_) async { connectedCompleter.complete(); - return PlatformBillingResult(responseCode: 0, debugMessage: ''); + return PlatformBillingResult( + responseCode: PlatformBillingResponse.ok, debugMessage: ''); }); final Completer calledCompleter1 = Completer(); @@ -64,7 +67,7 @@ void main() { await manager.runWithClientNonRetryable((_) async {}); manager.client.hostCallbackHandler.onBillingServiceDisconnected(0); - verify(mockApi.startConnection(any, any)).called(2); + verify(mockApi.startConnection(any, any, any)).called(2); }); test('re-connects when host calls reconnectWithBillingChoiceMode', @@ -83,11 +86,37 @@ void main() { manager.client.hostCallbackHandler.onBillingServiceDisconnected(0); // Verify that after connection ended reconnect was called. final VerificationResult result = - verify(mockApi.startConnection(any, captureAny)); + verify(mockApi.startConnection(any, captureAny, any)); expect(result.captured.single, PlatformBillingChoiceMode.alternativeBillingOnly); }); + test('re-connects when host calls reconnectWithPendingPurchasesParams', + () async { + // Ensures all asynchronous connected code finishes. + await manager.runWithClientNonRetryable((_) async {}); + + await manager.reconnectWithPendingPurchasesParams( + const PendingPurchasesParamsWrapper(enablePrepaidPlans: true)); + // Verify that connection was ended. + verify(mockApi.endConnection()).called(1); + + clearInteractions(mockApi); + + /// Fake the disconnect that we would expect from a endConnectionCall. + manager.client.hostCallbackHandler.onBillingServiceDisconnected(0); + // Verify that after connection ended reconnect was called. + final VerificationResult result = + verify(mockApi.startConnection(any, any, captureAny)); + expect( + result.captured.single, + isA().having( + (PlatformPendingPurchasesParams params) => + params.enablePrepaidPlans, + 'enablePrepaidPlans', + true)); + }); + test( 're-connects when operation returns BillingResponse.serviceDisconnected', () async { @@ -104,7 +133,7 @@ void main() { ); }, ); - verify(mockApi.startConnection(any, any)).called(1); + verify(mockApi.startConnection(any, any, any)).called(1); expect(timesCalled, equals(2)); expect(result.responseCode, equals(BillingResponse.ok)); }, @@ -113,7 +142,7 @@ void main() { test('does not re-connect when disposed', () { clearInteractions(mockApi); manager.dispose(); - verifyNever(mockApi.startConnection(any, any)); + verifyNever(mockApi.startConnection(any, any, any)); verify(mockApi.endConnection()).called(1); }); 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 0d94ddc7bf9..2b4989a43e9 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 @@ -7,7 +7,9 @@ import 'dart:async'; import 'package:flutter_test/flutter_test.dart'; import 'package:in_app_purchase_android/billing_client_wrappers.dart'; import 'package:in_app_purchase_android/src/billing_client_wrappers/billing_config_wrapper.dart'; +import 'package:in_app_purchase_android/src/billing_client_wrappers/pending_purchases_params_wrapper.dart'; import 'package:in_app_purchase_android/src/messages.g.dart'; +import 'package:in_app_purchase_android/src/pigeon_converters.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; @@ -39,8 +41,9 @@ void main() { setUp(() { mockApi = MockInAppPurchaseApi(); - when(mockApi.startConnection(any, any)).thenAnswer( - (_) async => PlatformBillingResult(responseCode: 0, debugMessage: '')); + when(mockApi.startConnection(any, any, any)).thenAnswer((_) async => + PlatformBillingResult( + responseCode: PlatformBillingResponse.ok, debugMessage: '')); billingClient = BillingClient( (PurchasesResultWrapper _) {}, (UserChoiceDetailsWrapper _) {}, api: mockApi); @@ -58,32 +61,13 @@ void main() { }); }); - // Make sure that the enum values are supported and that the converter call - // does not fail - test('response states', () async { - const BillingResponseConverter converter = BillingResponseConverter(); - converter.fromJson(-3); - converter.fromJson(-2); - converter.fromJson(-1); - converter.fromJson(0); - converter.fromJson(1); - converter.fromJson(2); - converter.fromJson(3); - converter.fromJson(4); - converter.fromJson(5); - converter.fromJson(6); - converter.fromJson(7); - converter.fromJson(8); - converter.fromJson(12); - }); - group('startConnection', () { test('returns BillingResultWrapper', () async { const String debugMessage = 'dummy message'; const BillingResponse responseCode = BillingResponse.developerError; - when(mockApi.startConnection(any, any)).thenAnswer( + when(mockApi.startConnection(any, any, any)).thenAnswer( (_) async => PlatformBillingResult( - responseCode: const BillingResponseConverter().toJson(responseCode), + responseCode: PlatformBillingResponse.developerError, debugMessage: debugMessage, ), ); @@ -100,9 +84,16 @@ void main() { await billingClient.startConnection(onBillingServiceDisconnected: () {}); final VerificationResult result = - verify(mockApi.startConnection(captureAny, captureAny)); + verify(mockApi.startConnection(captureAny, captureAny, captureAny)); expect(result.captured[0], 0); expect(result.captured[1], PlatformBillingChoiceMode.playBillingOnly); + expect( + result.captured[2], + isA().having( + (PlatformPendingPurchasesParams params) => + params.enablePrepaidPlans, + 'enablePrepaidPlans', + false)); }); test('passes billingChoiceMode alternativeBillingOnly when set', () async { @@ -110,7 +101,8 @@ void main() { onBillingServiceDisconnected: () {}, billingChoiceMode: BillingChoiceMode.alternativeBillingOnly); - expect(verify(mockApi.startConnection(any, captureAny)).captured.first, + expect( + verify(mockApi.startConnection(any, captureAny, any)).captured.first, PlatformBillingChoiceMode.alternativeBillingOnly); }); @@ -125,7 +117,8 @@ void main() { onBillingServiceDisconnected: () {}, billingChoiceMode: BillingChoiceMode.alternativeBillingOnly); - expect(verify(mockApi.startConnection(any, captureAny)).captured.first, + expect( + verify(mockApi.startConnection(any, captureAny, any)).captured.first, PlatformBillingChoiceMode.alternativeBillingOnly); const UserChoiceDetailsWrapper expected = UserChoiceDetailsWrapper( @@ -147,43 +140,20 @@ void main() { expect(await completer.future, expected); }); - test('UserChoiceDetailsWrapper searilization check', () async { - // Test ensures that changes to UserChoiceDetailsWrapper#toJson are - // compatible with code in Translator.java. - const String transactionIdKey = 'originalExternalTransactionId'; - const String transactionTokenKey = 'externalTransactionToken'; - const String productsKey = 'products'; - const String productIdKey = 'id'; - const String productOfferTokenKey = 'offerToken'; - const String productTypeKey = 'productType'; - - const UserChoiceDetailsProductWrapper expectedProduct1 = - UserChoiceDetailsProductWrapper( - id: 'id1', - offerToken: 'offerToken1', - productType: ProductType.inapp); - const UserChoiceDetailsProductWrapper expectedProduct2 = - UserChoiceDetailsProductWrapper( - id: 'id2', - offerToken: 'offerToken2', - productType: ProductType.inapp); - const UserChoiceDetailsWrapper expected = UserChoiceDetailsWrapper( - originalExternalTransactionId: 'TransactionId', - externalTransactionToken: 'TransactionToken', - products: [ - expectedProduct1, - expectedProduct2, - ], - ); - final Map detailsJson = expected.toJson(); - expect(detailsJson.keys, contains(transactionIdKey)); - expect(detailsJson.keys, contains(transactionTokenKey)); - expect(detailsJson.keys, contains(productsKey)); - - final Map productJson = expectedProduct1.toJson(); - expect(productJson, contains(productIdKey)); - expect(productJson, contains(productOfferTokenKey)); - expect(productJson, contains(productTypeKey)); + test('passes pendingPurchasesParams when set', () async { + await billingClient.startConnection( + onBillingServiceDisconnected: () {}, + billingChoiceMode: BillingChoiceMode.alternativeBillingOnly, + pendingPurchasesParams: + const PendingPurchasesParamsWrapper(enablePrepaidPlans: true)); + + expect( + verify(mockApi.startConnection(any, any, captureAny)).captured.first, + isA().having( + (PlatformPendingPurchasesParams params) => + params.enablePrepaidPlans, + 'enablePrepaidPlans', + true)); }); }); @@ -200,8 +170,7 @@ void main() { when(mockApi.queryProductDetailsAsync(any)) .thenAnswer((_) async => PlatformProductDetailsResponse( billingResult: PlatformBillingResult( - responseCode: - const BillingResponseConverter().toJson(responseCode), + responseCode: PlatformBillingResponse.developerError, debugMessage: debugMessage), productDetails: [], )); @@ -224,8 +193,7 @@ void main() { when(mockApi.queryProductDetailsAsync(any)) .thenAnswer((_) async => PlatformProductDetailsResponse( billingResult: PlatformBillingResult( - responseCode: - const BillingResponseConverter().toJson(responseCode), + responseCode: PlatformBillingResponse.ok, debugMessage: debugMessage), productDetails: [ convertToPigeonProductDetails(dummyOneTimeProductDetails) @@ -343,8 +311,8 @@ void main() { const ProductDetailsWrapper productDetails = dummyOneTimeProductDetails; const String accountId = 'hashedAccountId'; const String profileId = 'hashedProfileId'; - const ProrationMode prorationMode = - ProrationMode.immediateAndChargeProratedPrice; + const ReplacementMode replacementMode = + ReplacementMode.chargeProratedPrice; expect( await billingClient.launchBillingFlow( @@ -352,7 +320,7 @@ void main() { accountId: accountId, obfuscatedProfileId: profileId, oldProduct: dummyOldPurchase.products.first, - prorationMode: prorationMode, + replacementMode: replacementMode, purchaseToken: dummyOldPurchase.purchaseToken), equals(expectedBillingResult)); final VerificationResult result = @@ -364,8 +332,10 @@ void main() { expect(params.oldProduct, equals(dummyOldPurchase.products.first)); expect(params.obfuscatedProfileId, equals(profileId)); expect(params.purchaseToken, equals(dummyOldPurchase.purchaseToken)); - expect(params.prorationMode, - const ProrationModeConverter().toJson(prorationMode)); + expect( + params.replacementMode, + replacementModeFromWrapper(replacementMode), + ); }); test( @@ -380,8 +350,7 @@ void main() { const ProductDetailsWrapper productDetails = dummyOneTimeProductDetails; const String accountId = 'hashedAccountId'; const String profileId = 'hashedProfileId'; - const ProrationMode prorationMode = - ProrationMode.immediateAndChargeFullPrice; + const ReplacementMode replacementMode = ReplacementMode.chargeFullPrice; expect( await billingClient.launchBillingFlow( @@ -389,7 +358,7 @@ void main() { accountId: accountId, obfuscatedProfileId: profileId, oldProduct: dummyOldPurchase.products.first, - prorationMode: prorationMode, + replacementMode: replacementMode, purchaseToken: dummyOldPurchase.purchaseToken), equals(expectedBillingResult)); final VerificationResult result = @@ -401,8 +370,10 @@ void main() { expect(params.oldProduct, equals(dummyOldPurchase.products.first)); expect(params.obfuscatedProfileId, equals(profileId)); expect(params.purchaseToken, equals(dummyOldPurchase.purchaseToken)); - expect(params.prorationMode, - const ProrationModeConverter().toJson(prorationMode)); + expect( + params.replacementMode, + replacementModeFromWrapper(replacementMode), + ); }); test('handles null accountId', () async { @@ -439,8 +410,7 @@ void main() { when(mockApi.queryPurchasesAsync(any)) .thenAnswer((_) async => PlatformPurchasesResponse( billingResult: PlatformBillingResult( - responseCode: - const BillingResponseConverter().toJson(expectedCode), + responseCode: PlatformBillingResponse.ok, debugMessage: debugMessage), purchases: expectedList .map((PurchaseWrapper purchase) => @@ -464,8 +434,7 @@ void main() { when(mockApi.queryPurchasesAsync(any)) .thenAnswer((_) async => PlatformPurchasesResponse( billingResult: PlatformBillingResult( - responseCode: - const BillingResponseConverter().toJson(expectedCode), + responseCode: PlatformBillingResponse.userCanceled, debugMessage: debugMessage), purchases: [], )); @@ -482,32 +451,6 @@ void main() { }); group('queryPurchaseHistory', () { - test('serializes and deserializes data', () async { - const BillingResponse expectedCode = BillingResponse.ok; - final List expectedList = - [ - dummyPurchaseHistoryRecord, - ]; - const String debugMessage = 'dummy message'; - const BillingResultWrapper expectedBillingResult = BillingResultWrapper( - responseCode: expectedCode, debugMessage: debugMessage); - when(mockApi.queryPurchaseHistoryAsync(any)) - .thenAnswer((_) async => PlatformPurchaseHistoryResponse( - billingResult: PlatformBillingResult( - responseCode: - const BillingResponseConverter().toJson(expectedCode), - debugMessage: debugMessage), - purchases: expectedList - .map(platformPurchaseHistoryRecordFromWrapper) - .toList(), - )); - - final PurchasesHistoryResult response = - await billingClient.queryPurchaseHistory(ProductType.inapp); - expect(response.billingResult, equals(expectedBillingResult)); - expect(response.purchaseHistoryRecordList, equals(expectedList)); - }); - test('handles empty purchases', () async { const BillingResponse expectedCode = BillingResponse.userCanceled; const String debugMessage = 'dummy message'; @@ -516,8 +459,7 @@ void main() { when(mockApi.queryPurchaseHistoryAsync(any)) .thenAnswer((_) async => PlatformPurchaseHistoryResponse( billingResult: PlatformBillingResult( - responseCode: - const BillingResponseConverter().toJson(expectedCode), + responseCode: PlatformBillingResponse.userCanceled, debugMessage: debugMessage), purchases: [], )); @@ -566,7 +508,8 @@ void main() { group('isFeatureSupported', () { test('isFeatureSupported returns false', () async { - when(mockApi.isFeatureSupported('subscriptions')) + when(mockApi + .isFeatureSupported(PlatformBillingClientFeature.subscriptions)) .thenAnswer((_) async => false); final bool isSupported = await billingClient .isFeatureSupported(BillingClientFeature.subscriptions); @@ -574,7 +517,8 @@ void main() { }); test('isFeatureSupported returns true', () async { - when(mockApi.isFeatureSupported('subscriptions')) + when(mockApi + .isFeatureSupported(PlatformBillingClientFeature.subscriptions)) .thenAnswer((_) async => true); final bool isSupported = await billingClient .isFeatureSupported(BillingClientFeature.subscriptions); @@ -603,7 +547,8 @@ void main() { responseCode: BillingResponse.ok, debugMessage: 'message'); when(mockApi.isAlternativeBillingOnlyAvailableAsync()).thenAnswer( (_) async => PlatformBillingResult( - responseCode: 0, debugMessage: expected.debugMessage!)); + responseCode: PlatformBillingResponse.ok, + debugMessage: expected.debugMessage!)); final BillingResultWrapper result = await billingClient.isAlternativeBillingOnlyAvailable(); expect(result, expected); @@ -633,7 +578,8 @@ void main() { responseCode: BillingResponse.ok, debugMessage: 'message'); when(mockApi.showAlternativeBillingOnlyInformationDialog()).thenAnswer( (_) async => PlatformBillingResult( - responseCode: 0, debugMessage: expected.debugMessage!)); + responseCode: PlatformBillingResponse.ok, + debugMessage: expected.debugMessage!)); final BillingResultWrapper result = await billingClient.showAlternativeBillingOnlyInformationDialog(); expect(result, expected); @@ -645,8 +591,7 @@ PlatformBillingConfigResponse platformBillingConfigFromWrapper( BillingConfigWrapper original) { return PlatformBillingConfigResponse( billingResult: PlatformBillingResult( - responseCode: - const BillingResponseConverter().toJson(original.responseCode), + responseCode: billingResponseFromWrapper(original.responseCode), debugMessage: original.debugMessage!, ), countryCode: original.countryCode); @@ -657,24 +602,8 @@ PlatformAlternativeBillingOnlyReportingDetailsResponse AlternativeBillingOnlyReportingDetailsWrapper original) { return PlatformAlternativeBillingOnlyReportingDetailsResponse( billingResult: PlatformBillingResult( - responseCode: - const BillingResponseConverter().toJson(original.responseCode), + responseCode: billingResponseFromWrapper(original.responseCode), debugMessage: original.debugMessage!, ), externalTransactionToken: original.externalTransactionToken); } - -PlatformPurchaseHistoryRecord platformPurchaseHistoryRecordFromWrapper( - PurchaseHistoryRecordWrapper wrapper) { - return PlatformPurchaseHistoryRecord( - // For some reason quantity is not currently exposed in - // PurchaseHistoryRecordWrapper. - quantity: 99, - purchaseTime: wrapper.purchaseTime, - originalJson: wrapper.originalJson, - purchaseToken: wrapper.purchaseToken, - signature: wrapper.signature, - products: wrapper.products, - developerPayload: wrapper.developerPayload, - ); -} diff --git a/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/billing_client_wrapper_test.mocks.dart b/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/billing_client_wrapper_test.mocks.dart index 5a93a96f975..a5086ce40a6 100644 --- a/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/billing_client_wrapper_test.mocks.dart +++ b/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/billing_client_wrapper_test.mocks.dart @@ -120,6 +120,7 @@ class MockInAppPurchaseApi extends _i1.Mock implements _i2.InAppPurchaseApi { _i4.Future<_i2.PlatformBillingResult> startConnection( int? callbackHandle, _i2.PlatformBillingChoiceMode? billingMode, + _i2.PlatformPendingPurchasesParams? pendingPurchasesParams, ) => (super.noSuchMethod( Invocation.method( @@ -127,6 +128,7 @@ class MockInAppPurchaseApi extends _i1.Mock implements _i2.InAppPurchaseApi { [ callbackHandle, billingMode, + pendingPurchasesParams, ], ), returnValue: _i4.Future<_i2.PlatformBillingResult>.value( @@ -137,6 +139,7 @@ class MockInAppPurchaseApi extends _i1.Mock implements _i2.InAppPurchaseApi { [ callbackHandle, billingMode, + pendingPurchasesParams, ], ), )), @@ -148,6 +151,7 @@ class MockInAppPurchaseApi extends _i1.Mock implements _i2.InAppPurchaseApi { [ callbackHandle, billingMode, + pendingPurchasesParams, ], ), )), @@ -348,7 +352,9 @@ class MockInAppPurchaseApi extends _i1.Mock implements _i2.InAppPurchaseApi { ) as _i4.Future<_i2.PlatformProductDetailsResponse>); @override - _i4.Future isFeatureSupported(String? feature) => (super.noSuchMethod( + _i4.Future isFeatureSupported( + _i2.PlatformBillingClientFeature? feature) => + (super.noSuchMethod( Invocation.method( #isFeatureSupported, [feature], diff --git a/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/product_details_wrapper_test.dart b/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/product_details_wrapper_test.dart index 3bd6a497490..877a0d1259e 100644 --- a/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/product_details_wrapper_test.dart +++ b/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/product_details_wrapper_test.dart @@ -19,89 +19,14 @@ const ProductDetailsWrapper dummyOneTimeProductDetails = ProductDetailsWrapper( ), ); -const ProductDetailsWrapper dummySubscriptionProductDetails = - ProductDetailsWrapper( - description: 'description', - name: 'name', - productId: 'productId', - productType: ProductType.subs, - title: 'title', - subscriptionOfferDetails: [ - SubscriptionOfferDetailsWrapper( - basePlanId: 'basePlanId', - offerTags: ['offerTags'], - offerId: 'offerId', - offerIdToken: 'offerToken', - pricingPhases: [ - PricingPhaseWrapper( - billingCycleCount: 4, - billingPeriod: 'billingPeriod', - formattedPrice: r'$100', - priceAmountMicros: 100000000, - priceCurrencyCode: 'USD', - recurrenceMode: RecurrenceMode.finiteRecurring, - ), - ], - ), - ], -); - void main() { - group('ProductDetailsWrapper', () { - test('converts one-time purchase from map', () { - const ProductDetailsWrapper expected = dummyOneTimeProductDetails; - final ProductDetailsWrapper parsed = - ProductDetailsWrapper.fromJson(buildProductMap(expected)); - - expect(parsed, equals(expected)); - }); - - test('converts subscription from map', () { - const ProductDetailsWrapper expected = dummySubscriptionProductDetails; - final ProductDetailsWrapper parsed = - ProductDetailsWrapper.fromJson(buildProductMap(expected)); - - expect(parsed, equals(expected)); - }); - }); - group('ProductDetailsResponseWrapper', () { - test('parsed from map', () { - const BillingResponse responseCode = BillingResponse.ok; - const String debugMessage = 'dummy message'; - final List productsDetails = - [ - dummyOneTimeProductDetails, - dummyOneTimeProductDetails, - ]; - const BillingResultWrapper result = BillingResultWrapper( - responseCode: responseCode, debugMessage: debugMessage); - final ProductDetailsResponseWrapper expected = - ProductDetailsResponseWrapper( - billingResult: result, productDetailsList: productsDetails); - - final ProductDetailsResponseWrapper parsed = - ProductDetailsResponseWrapper.fromJson({ - 'billingResult': { - 'responseCode': const BillingResponseConverter().toJson(responseCode), - 'debugMessage': debugMessage, - }, - 'productDetailsList': >[ - buildProductMap(dummyOneTimeProductDetails), - buildProductMap(dummyOneTimeProductDetails), - ], - }); - - expect(parsed.billingResult, equals(expected.billingResult)); - expect( - parsed.productDetailsList, containsAll(expected.productDetailsList)); - }); - test('toProductDetails() should return correct Product object', () { - final ProductDetailsWrapper wrapper = ProductDetailsWrapper.fromJson( - buildProductMap(dummyOneTimeProductDetails)); + const ProductDetailsWrapper wrapper = dummyOneTimeProductDetails; final GooglePlayProductDetails product = - GooglePlayProductDetails.fromProductDetails(wrapper).first; + GooglePlayProductDetails.fromProductDetails( + dummyOneTimeProductDetails) + .first; expect(product.title, wrapper.title); expect(product.description, wrapper.description); expect(product.id, wrapper.productId); @@ -109,60 +34,9 @@ void main() { product.price, wrapper.oneTimePurchaseOfferDetails?.formattedPrice); expect(product.productDetails, wrapper); }); - - test('handles empty list of productDetails', () { - const BillingResponse responseCode = BillingResponse.error; - const String debugMessage = 'dummy message'; - final List productsDetails = - []; - const BillingResultWrapper billingResult = BillingResultWrapper( - responseCode: responseCode, debugMessage: debugMessage); - final ProductDetailsResponseWrapper expected = - ProductDetailsResponseWrapper( - billingResult: billingResult, - productDetailsList: productsDetails); - - final ProductDetailsResponseWrapper parsed = - ProductDetailsResponseWrapper.fromJson({ - 'billingResult': { - 'responseCode': const BillingResponseConverter().toJson(responseCode), - 'debugMessage': debugMessage, - }, - 'productDetailsList': const >[] - }); - - expect(parsed.billingResult, equals(expected.billingResult)); - expect( - parsed.productDetailsList, containsAll(expected.productDetailsList)); - }); - - test('fromJson creates an object with default values', () { - final ProductDetailsResponseWrapper productDetails = - ProductDetailsResponseWrapper.fromJson(const {}); - expect( - productDetails.billingResult, - equals(const BillingResultWrapper( - responseCode: BillingResponse.error, - debugMessage: kInvalidBillingResultErrorMessage))); - expect(productDetails.productDetailsList, isEmpty); - }); }); group('BillingResultWrapper', () { - test('fromJson on empty map creates an object with default values', () { - final BillingResultWrapper billingResult = - BillingResultWrapper.fromJson(const {}); - expect(billingResult.debugMessage, kInvalidBillingResultErrorMessage); - expect(billingResult.responseCode, BillingResponse.error); - }); - - test('fromJson on null creates an object with default values', () { - final BillingResultWrapper billingResult = - BillingResultWrapper.fromJson(null); - expect(billingResult.debugMessage, kInvalidBillingResultErrorMessage); - expect(billingResult.responseCode, BillingResponse.error); - }); - test('operator == of ProductDetailsWrapper works fine', () { const ProductDetailsWrapper firstProductDetailsInstance = ProductDetailsWrapper( @@ -191,6 +65,10 @@ void main() { recurrenceMode: RecurrenceMode.finiteRecurring, ), ], + installmentPlanDetails: InstallmentPlanDetailsWrapper( + commitmentPaymentsCount: 1, + subsequentCommitmentPaymentsCount: 2, + ), ), ], ); @@ -221,6 +99,10 @@ void main() { recurrenceMode: RecurrenceMode.finiteRecurring, ), ], + installmentPlanDetails: InstallmentPlanDetailsWrapper( + commitmentPaymentsCount: 1, + subsequentCommitmentPaymentsCount: 2, + ), ), ], ); @@ -243,73 +125,3 @@ void main() { }); }); } - -Map buildProductMap(ProductDetailsWrapper original) { - final Map map = { - 'title': original.title, - 'description': original.description, - 'productId': original.productId, - 'productType': const ProductTypeConverter().toJson(original.productType), - 'name': original.name, - }; - - if (original.oneTimePurchaseOfferDetails != null) { - map.putIfAbsent('oneTimePurchaseOfferDetails', - () => buildOneTimePurchaseMap(original.oneTimePurchaseOfferDetails!)); - } - - if (original.subscriptionOfferDetails != null) { - map.putIfAbsent('subscriptionOfferDetails', - () => buildSubscriptionMapList(original.subscriptionOfferDetails!)); - } - - return map; -} - -Map buildOneTimePurchaseMap( - OneTimePurchaseOfferDetailsWrapper original) { - return { - 'priceAmountMicros': original.priceAmountMicros, - 'priceCurrencyCode': original.priceCurrencyCode, - 'formattedPrice': original.formattedPrice, - }; -} - -List> buildSubscriptionMapList( - List original) { - return original - .map((SubscriptionOfferDetailsWrapper subscriptionOfferDetails) => - buildSubscriptionMap(subscriptionOfferDetails)) - .toList(); -} - -Map buildSubscriptionMap( - SubscriptionOfferDetailsWrapper original) { - return { - 'offerId': original.offerId, - 'basePlanId': original.basePlanId, - 'offerTags': original.offerTags, - 'offerIdToken': original.offerIdToken, - 'pricingPhases': buildPricingPhaseMapList(original.pricingPhases), - }; -} - -List> buildPricingPhaseMapList( - List original) { - return original - .map((PricingPhaseWrapper pricingPhase) => - buildPricingPhaseMap(pricingPhase)) - .toList(); -} - -Map buildPricingPhaseMap(PricingPhaseWrapper original) { - return { - 'formattedPrice': original.formattedPrice, - 'priceCurrencyCode': original.priceCurrencyCode, - 'priceAmountMicros': original.priceAmountMicros, - 'billingCycleCount': original.billingCycleCount, - 'billingPeriod': original.billingPeriod, - 'recurrenceMode': - const RecurrenceModeConverter().toJson(original.recurrenceMode), - }; -} diff --git a/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/product_wrapper_test.dart b/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/product_wrapper_test.dart deleted file mode 100644 index d9fe397b525..00000000000 --- a/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/product_wrapper_test.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:in_app_purchase_android/billing_client_wrappers.dart'; -import 'package:test/test.dart'; - -const ProductWrapper dummyProduct = ProductWrapper( - productId: 'id', - productType: ProductType.inapp, -); - -void main() { - group('ProductWrapper', () { - test('converts product from map', () { - const ProductWrapper expected = dummyProduct; - final ProductWrapper parsed = productFromJson(expected.toJson()); - - expect(parsed, equals(expected)); - }); - }); -} - -ProductWrapper productFromJson(Map serialized) { - return ProductWrapper( - productId: serialized['productId'] as String, - productType: const ProductTypeConverter() - .fromJson(serialized['productType'] as String), - ); -} diff --git a/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/purchase_wrapper_test.dart b/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/purchase_wrapper_test.dart index 14cd446bf8a..71449d7e378 100644 --- a/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/purchase_wrapper_test.dart +++ b/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/purchase_wrapper_test.dart @@ -50,26 +50,8 @@ const PurchaseWrapper dummyUnacknowledgedPurchase = PurchaseWrapper( purchaseState: PurchaseStateWrapper.purchased, ); -const PurchaseHistoryRecordWrapper dummyPurchaseHistoryRecord = - PurchaseHistoryRecordWrapper( - purchaseTime: 0, - signature: 'signature', - products: ['product'], - purchaseToken: 'purchaseToken', - originalJson: '', - developerPayload: 'dummy payload', -); - void main() { group('PurchaseWrapper', () { - test('converts from map', () { - const PurchaseWrapper expected = dummyPurchase; - final PurchaseWrapper parsed = - PurchaseWrapper.fromJson(buildPurchaseMap(expected)); - - expect(parsed, equals(expected)); - }); - test('fromPurchase() should return correct PurchaseDetail object', () { final List details = GooglePlayPurchaseDetails.fromPurchase(dummyMultipleProductsPurchase); @@ -121,134 +103,4 @@ void main() { expect(details.pendingCompletePurchase, true); }); }); - - group('PurchaseHistoryRecordWrapper', () { - test('converts from map', () { - const PurchaseHistoryRecordWrapper expected = dummyPurchaseHistoryRecord; - final PurchaseHistoryRecordWrapper parsed = - PurchaseHistoryRecordWrapper.fromJson( - buildPurchaseHistoryRecordMap(expected)); - - expect(parsed, equals(expected)); - }); - }); - - group('PurchasesResultWrapper', () { - test('parsed from map', () { - const BillingResponse responseCode = BillingResponse.ok; - final List purchases = [ - dummyPurchase, - dummyPurchase - ]; - const String debugMessage = 'dummy Message'; - const BillingResultWrapper billingResult = BillingResultWrapper( - responseCode: responseCode, debugMessage: debugMessage); - final PurchasesResultWrapper expected = PurchasesResultWrapper( - billingResult: billingResult, - responseCode: responseCode, - purchasesList: purchases); - final PurchasesResultWrapper parsed = - PurchasesResultWrapper.fromJson({ - 'billingResult': buildBillingResultMap(billingResult), - 'responseCode': const BillingResponseConverter().toJson(responseCode), - 'purchasesList': >[ - buildPurchaseMap(dummyPurchase), - buildPurchaseMap(dummyPurchase) - ] - }); - expect(parsed.billingResult, equals(expected.billingResult)); - expect(parsed.responseCode, equals(expected.responseCode)); - expect(parsed.purchasesList, containsAll(expected.purchasesList)); - }); - - test('parsed from empty map', () { - final PurchasesResultWrapper parsed = - PurchasesResultWrapper.fromJson(const {}); - expect( - parsed.billingResult, - equals(const BillingResultWrapper( - responseCode: BillingResponse.error, - debugMessage: kInvalidBillingResultErrorMessage))); - expect(parsed.responseCode, BillingResponse.error); - expect(parsed.purchasesList, isEmpty); - }); - }); - - group('PurchasesHistoryResult', () { - test('parsed from map', () { - const BillingResponse responseCode = BillingResponse.ok; - final List purchaseHistoryRecordList = - [ - dummyPurchaseHistoryRecord, - dummyPurchaseHistoryRecord - ]; - const String debugMessage = 'dummy Message'; - const BillingResultWrapper billingResult = BillingResultWrapper( - responseCode: responseCode, debugMessage: debugMessage); - final PurchasesHistoryResult expected = PurchasesHistoryResult( - billingResult: billingResult, - purchaseHistoryRecordList: purchaseHistoryRecordList); - final PurchasesHistoryResult parsed = - PurchasesHistoryResult.fromJson({ - 'billingResult': buildBillingResultMap(billingResult), - 'purchaseHistoryRecordList': >[ - buildPurchaseHistoryRecordMap(dummyPurchaseHistoryRecord), - buildPurchaseHistoryRecordMap(dummyPurchaseHistoryRecord) - ] - }); - expect(parsed.billingResult, equals(billingResult)); - expect(parsed.purchaseHistoryRecordList, - containsAll(expected.purchaseHistoryRecordList)); - }); - - test('parsed from empty map', () { - final PurchasesHistoryResult parsed = - PurchasesHistoryResult.fromJson(const {}); - expect( - parsed.billingResult, - equals(const BillingResultWrapper( - responseCode: BillingResponse.error, - debugMessage: kInvalidBillingResultErrorMessage))); - expect(parsed.purchaseHistoryRecordList, isEmpty); - }); - }); -} - -Map buildPurchaseMap(PurchaseWrapper original) { - return { - 'orderId': original.orderId, - 'packageName': original.packageName, - 'purchaseTime': original.purchaseTime, - 'signature': original.signature, - 'products': original.products, - 'purchaseToken': original.purchaseToken, - 'isAutoRenewing': original.isAutoRenewing, - 'originalJson': original.originalJson, - 'developerPayload': original.developerPayload, - 'purchaseState': - const PurchaseStateConverter().toJson(original.purchaseState), - 'isAcknowledged': original.isAcknowledged, - 'obfuscatedAccountId': original.obfuscatedAccountId, - 'obfuscatedProfileId': original.obfuscatedProfileId, - }; -} - -Map buildPurchaseHistoryRecordMap( - PurchaseHistoryRecordWrapper original) { - return { - 'purchaseTime': original.purchaseTime, - 'signature': original.signature, - 'products': original.products, - 'purchaseToken': original.purchaseToken, - 'originalJson': original.originalJson, - 'developerPayload': original.developerPayload, - }; -} - -Map buildBillingResultMap(BillingResultWrapper original) { - return { - 'responseCode': - const BillingResponseConverter().toJson(original.responseCode), - 'debugMessage': original.debugMessage, - }; } 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 a9c76ab5afe..38c2deaf692 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 @@ -27,8 +27,9 @@ void main() { setUp(() { widgets.WidgetsFlutterBinding.ensureInitialized(); mockApi = MockInAppPurchaseApi(); - when(mockApi.startConnection(any, any)).thenAnswer( - (_) async => PlatformBillingResult(responseCode: 0, debugMessage: '')); + when(mockApi.startConnection(any, any, any)).thenAnswer((_) async => + PlatformBillingResult( + responseCode: PlatformBillingResponse.ok, debugMessage: '')); manager = BillingClientManager( billingClientFactory: (PurchasesUpdatedListener listener, UserSelectedAlternativeBillingListener? @@ -80,7 +81,7 @@ void main() { manager.client.hostCallbackHandler.onBillingServiceDisconnected(0); // Verify that after connection ended reconnect was called. final VerificationResult result = - verify(mockApi.startConnection(any, captureAny)); + verify(mockApi.startConnection(any, captureAny, any)); expect(result.callCount, equals(2)); expect(result.captured.last, PlatformBillingChoiceMode.alternativeBillingOnly); @@ -95,7 +96,7 @@ void main() { manager.client.hostCallbackHandler.onBillingServiceDisconnected(0); // Verify that after connection ended reconnect was called. final VerificationResult result = - verify(mockApi.startConnection(any, captureAny)); + verify(mockApi.startConnection(any, captureAny, any)); expect(result.callCount, equals(2)); expect(result.captured.last, PlatformBillingChoiceMode.playBillingOnly); }); @@ -107,7 +108,8 @@ void main() { responseCode: BillingResponse.ok, debugMessage: 'dummy message'); when(mockApi.isAlternativeBillingOnlyAvailableAsync()).thenAnswer( (_) async => PlatformBillingResult( - responseCode: 0, debugMessage: expected.debugMessage!)); + responseCode: PlatformBillingResponse.ok, + debugMessage: expected.debugMessage!)); final BillingResultWrapper result = await iapAndroidPlatformAddition.isAlternativeBillingOnlyAvailable(); @@ -136,14 +138,12 @@ void main() { group('queryPurchaseDetails', () { test('returns ProductDetailsResponseWrapper', () async { const String debugMessage = 'dummy message'; - const BillingResponse responseCode = BillingResponse.ok; + const PlatformBillingResponse responseCode = PlatformBillingResponse.ok; when(mockApi.queryPurchasesAsync(any)) .thenAnswer((_) async => PlatformPurchasesResponse( billingResult: PlatformBillingResult( - responseCode: - const BillingResponseConverter().toJson(responseCode), - debugMessage: debugMessage), + responseCode: responseCode, debugMessage: debugMessage), purchases: [ convertToPigeonPurchase(dummyPurchase), ], @@ -179,7 +179,8 @@ void main() { group('isFeatureSupported', () { test('isFeatureSupported returns false', () async { - when(mockApi.isFeatureSupported('subscriptions')) + when(mockApi + .isFeatureSupported(PlatformBillingClientFeature.subscriptions)) .thenAnswer((_) async => false); final bool isSupported = await iapAndroidPlatformAddition .isFeatureSupported(BillingClientFeature.subscriptions); @@ -187,7 +188,8 @@ void main() { }); test('isFeatureSupported returns true', () async { - when(mockApi.isFeatureSupported('subscriptions')) + when(mockApi + .isFeatureSupported(PlatformBillingClientFeature.subscriptions)) .thenAnswer((_) async => true); final bool isSupported = await iapAndroidPlatformAddition .isFeatureSupported(BillingClientFeature.subscriptions); 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 ee183eeba71..4bd23f244e6 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 @@ -30,8 +30,9 @@ void main() { widgets.WidgetsFlutterBinding.ensureInitialized(); mockApi = MockInAppPurchaseApi(); - when(mockApi.startConnection(any, any)).thenAnswer( - (_) async => PlatformBillingResult(responseCode: 0, debugMessage: '')); + when(mockApi.startConnection(any, any, any)).thenAnswer((_) async => + PlatformBillingResult( + responseCode: PlatformBillingResponse.ok, debugMessage: '')); iapAndroidPlatform = InAppPurchaseAndroidPlatform( manager: BillingClientManager( billingClientFactory: (PurchasesUpdatedListener listener, @@ -45,13 +46,13 @@ void main() { group('connection management', () { test('connects on initialization', () { //await iapAndroidPlatform.isAvailable(); - verify(mockApi.startConnection(any, any)).called(1); + verify(mockApi.startConnection(any, any, any)).called(1); }); test('re-connects when client sends onBillingServiceDisconnected', () { iapAndroidPlatform.billingClientManager.client.hostCallbackHandler .onBillingServiceDisconnected(0); - verify(mockApi.startConnection(any, any)).called(2); + verify(mockApi.startConnection(any, any, any)).called(2); }); test( @@ -59,19 +60,18 @@ void main() { () async { when(mockApi.acknowledgePurchase(any)).thenAnswer( (_) async => PlatformBillingResult( - responseCode: const BillingResponseConverter() - .toJson(BillingResponse.serviceDisconnected), + responseCode: PlatformBillingResponse.serviceDisconnected, debugMessage: 'disconnected'), ); - when(mockApi.startConnection(any, any)).thenAnswer((_) async { + when(mockApi.startConnection(any, any, any)).thenAnswer((_) async { // Change the acknowledgePurchase response to success for the next call. when(mockApi.acknowledgePurchase(any)).thenAnswer( (_) async => PlatformBillingResult( - responseCode: - const BillingResponseConverter().toJson(BillingResponse.ok), + responseCode: PlatformBillingResponse.ok, debugMessage: 'disconnected'), ); - return PlatformBillingResult(responseCode: 0, debugMessage: ''); + return PlatformBillingResult( + responseCode: PlatformBillingResponse.ok, debugMessage: ''); }); final PurchaseDetails purchase = GooglePlayPurchaseDetails.fromPurchase(dummyUnacknowledgedPurchase) @@ -79,7 +79,7 @@ void main() { final BillingResultWrapper result = await iapAndroidPlatform.completePurchase(purchase); verify(mockApi.acknowledgePurchase(any)).called(2); - verify(mockApi.startConnection(any, any)).called(2); + verify(mockApi.startConnection(any, any, any)).called(2); expect(result.responseCode, equals(BillingResponse.ok)); }); }); @@ -99,13 +99,11 @@ void main() { group('queryProductDetails', () { test('handles empty productDetails', () async { const String debugMessage = 'dummy message'; - const BillingResponse responseCode = BillingResponse.ok; + const PlatformBillingResponse responseCode = PlatformBillingResponse.ok; when(mockApi.queryProductDetailsAsync(any)) .thenAnswer((_) async => PlatformProductDetailsResponse( billingResult: PlatformBillingResult( - responseCode: - const BillingResponseConverter().toJson(responseCode), - debugMessage: debugMessage), + responseCode: responseCode, debugMessage: debugMessage), productDetails: [], )); @@ -116,13 +114,11 @@ void main() { test('should get correct product details', () async { const String debugMessage = 'dummy message'; - const BillingResponse responseCode = BillingResponse.ok; + const PlatformBillingResponse responseCode = PlatformBillingResponse.ok; when(mockApi.queryProductDetailsAsync(any)) .thenAnswer((_) async => PlatformProductDetailsResponse( billingResult: PlatformBillingResult( - responseCode: - const BillingResponseConverter().toJson(responseCode), - debugMessage: debugMessage), + responseCode: responseCode, debugMessage: debugMessage), productDetails: [ convertToPigeonProductDetails(dummyOneTimeProductDetails) ], @@ -144,13 +140,11 @@ void main() { test('should get the correct notFoundIDs', () async { const String debugMessage = 'dummy message'; - const BillingResponse responseCode = BillingResponse.ok; + const PlatformBillingResponse responseCode = PlatformBillingResponse.ok; when(mockApi.queryProductDetailsAsync(any)) .thenAnswer((_) async => PlatformProductDetailsResponse( billingResult: PlatformBillingResult( - responseCode: - const BillingResponseConverter().toJson(responseCode), - debugMessage: debugMessage), + responseCode: responseCode, debugMessage: debugMessage), productDetails: [ convertToPigeonProductDetails(dummyOneTimeProductDetails) ], @@ -224,14 +218,12 @@ void main() { }); const String debugMessage = 'dummy message'; - const BillingResponse responseCode = BillingResponse.ok; + const PlatformBillingResponse responseCode = PlatformBillingResponse.ok; when(mockApi.queryPurchasesAsync(any)) .thenAnswer((_) async => PlatformPurchasesResponse( billingResult: PlatformBillingResult( - responseCode: - const BillingResponseConverter().toJson(responseCode), - debugMessage: debugMessage), + responseCode: responseCode, debugMessage: debugMessage), purchases: [ convertToPigeonPurchase(dummyPurchase), ], @@ -717,7 +709,7 @@ void main() { oldPurchaseDetails: GooglePlayPurchaseDetails.fromPurchase( dummyUnacknowledgedPurchase) .first, - prorationMode: ProrationMode.deferred, + replacementMode: ReplacementMode.deferred, )); await iapAndroidPlatform.buyNonConsumable(purchaseParam: purchaseParam); diff --git a/packages/in_app_purchase/in_app_purchase_android/test/test_conversion_utils.dart b/packages/in_app_purchase/in_app_purchase_android/test/test_conversion_utils.dart index 483c7ee257f..9c0bfae2aa7 100644 --- a/packages/in_app_purchase/in_app_purchase_android/test/test_conversion_utils.dart +++ b/packages/in_app_purchase/in_app_purchase_android/test/test_conversion_utils.dart @@ -13,8 +13,7 @@ import 'package:in_app_purchase_android/src/pigeon_converters.dart'; /// target must have a non-null string as well. PlatformBillingResult convertToPigeonResult(BillingResultWrapper targetResult) { return PlatformBillingResult( - responseCode: - const BillingResponseConverter().toJson(targetResult.responseCode), + responseCode: billingResponseFromWrapper(targetResult.responseCode), debugMessage: targetResult.debugMessage!, ); }