blob: 4110754da57bff22430a6b2a891167a2baa65cda [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:ui' show hashValues;
import 'package:collection/collection.dart';
import 'package:json_annotation/json_annotation.dart';
import 'enum_converters.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 'sk_product_wrapper.g.dart';
/// Dart wrapper around StoreKit's [SKProductsResponse](https://developer.apple.com/documentation/storekit/skproductsresponse?language=objc).
///
/// Represents the response object returned by [SKRequestMaker.startProductRequest].
/// Contains information about a list of products and a list of invalid product identifiers.
@JsonSerializable()
class SkProductResponseWrapper {
/// Creates an [SkProductResponseWrapper] with the given product details.
SkProductResponseWrapper(
{required this.products, required this.invalidProductIdentifiers});
/// Constructing an instance from a map from the Objective-C layer.
///
/// This method should only be used with `map` values returned by [SKRequestMaker.startProductRequest].
factory SkProductResponseWrapper.fromJson(Map<String, dynamic> map) {
return _$SkProductResponseWrapperFromJson(map);
}
/// Stores all matching successfully found products.
///
/// One product in this list matches one valid product identifier passed to the [SKRequestMaker.startProductRequest].
/// Will be empty if the [SKRequestMaker.startProductRequest] method does not pass any correct product identifier.
@JsonKey(defaultValue: <SKProductWrapper>[])
final List<SKProductWrapper> products;
/// Stores product identifiers in the `productIdentifiers` from [SKRequestMaker.startProductRequest] that are not recognized by the App Store.
///
/// The App Store will not recognize a product identifier unless certain criteria are met. A detailed list of the criteria can be
/// found here https://developer.apple.com/documentation/storekit/skproductsresponse/1505985-invalidproductidentifiers?language=objc.
/// Will be empty if all the product identifiers are valid.
@JsonKey(defaultValue: <String>[])
final List<String> invalidProductIdentifiers;
@override
bool operator ==(Object other) {
if (identical(other, this)) {
return true;
}
if (other.runtimeType != runtimeType) {
return false;
}
final SkProductResponseWrapper typedOther =
other as SkProductResponseWrapper;
return DeepCollectionEquality().equals(typedOther.products, products) &&
DeepCollectionEquality().equals(
typedOther.invalidProductIdentifiers, invalidProductIdentifiers);
}
@override
int get hashCode => hashValues(this.products, this.invalidProductIdentifiers);
}
/// Dart wrapper around StoreKit's [SKProductPeriodUnit](https://developer.apple.com/documentation/storekit/skproductperiodunit?language=objc).
///
/// Used as a property in the [SKProductSubscriptionPeriodWrapper]. Minimum is a day and maximum is a year.
// The values of the enum options are matching the [SKProductPeriodUnit]'s values. Should there be an update or addition
// in the [SKProductPeriodUnit], this need to be updated to match.
enum SKSubscriptionPeriodUnit {
/// An interval lasting one day.
@JsonValue(0)
day,
/// An interval lasting one month.
@JsonValue(1)
/// An interval lasting one week.
week,
@JsonValue(2)
/// An interval lasting one month.
month,
/// An interval lasting one year.
@JsonValue(3)
year,
}
/// Dart wrapper around StoreKit's [SKProductSubscriptionPeriod](https://developer.apple.com/documentation/storekit/skproductsubscriptionperiod?language=objc).
///
/// A period is defined by a [numberOfUnits] and a [unit], e.g for a 3 months period [numberOfUnits] is 3 and [unit] is a month.
/// It is used as a property in [SKProductDiscountWrapper] and [SKProductWrapper].
@JsonSerializable()
class SKProductSubscriptionPeriodWrapper {
/// Creates an [SKProductSubscriptionPeriodWrapper] for a `numberOfUnits`x`unit` period.
SKProductSubscriptionPeriodWrapper(
{required this.numberOfUnits, required this.unit});
/// Constructing an instance from a map from the Objective-C layer.
///
/// This method should only be used with `map` values returned by [SKProductDiscountWrapper.fromJson] or [SKProductWrapper.fromJson].
factory SKProductSubscriptionPeriodWrapper.fromJson(
Map<String, dynamic>? map) {
if (map == null) {
return SKProductSubscriptionPeriodWrapper(
numberOfUnits: 0, unit: SKSubscriptionPeriodUnit.day);
}
return _$SKProductSubscriptionPeriodWrapperFromJson(map);
}
/// The number of [unit] units in this period.
///
/// Must be greater than 0 if the object is valid.
@JsonKey(defaultValue: 0)
final int numberOfUnits;
/// The time unit used to specify the length of this period.
@SKSubscriptionPeriodUnitConverter()
final SKSubscriptionPeriodUnit unit;
@override
bool operator ==(Object other) {
if (identical(other, this)) {
return true;
}
if (other.runtimeType != runtimeType) {
return false;
}
final SKProductSubscriptionPeriodWrapper typedOther =
other as SKProductSubscriptionPeriodWrapper;
return typedOther.numberOfUnits == numberOfUnits && typedOther.unit == unit;
}
@override
int get hashCode => hashValues(this.numberOfUnits, this.unit);
}
/// Dart wrapper around StoreKit's [SKProductDiscountPaymentMode](https://developer.apple.com/documentation/storekit/skproductdiscountpaymentmode?language=objc).
///
/// This is used as a property in the [SKProductDiscountWrapper].
// The values of the enum options are matching the [SKProductDiscountPaymentMode]'s values. Should there be an update or addition
// in the [SKProductDiscountPaymentMode], this need to be updated to match.
enum SKProductDiscountPaymentMode {
/// Allows user to pay the discounted price at each payment period.
@JsonValue(0)
payAsYouGo,
/// Allows user to pay the discounted price upfront and receive the product for the rest of time that was paid for.
@JsonValue(1)
payUpFront,
/// User pays nothing during the discounted period.
@JsonValue(2)
freeTrail,
/// Unspecified mode.
@JsonValue(-1)
unspecified,
}
/// Dart wrapper around StoreKit's [SKProductDiscount](https://developer.apple.com/documentation/storekit/skproductdiscount?language=objc).
///
/// It is used as a property in [SKProductWrapper].
@JsonSerializable()
class SKProductDiscountWrapper {
/// Creates an [SKProductDiscountWrapper] with the given discount details.
SKProductDiscountWrapper(
{required this.price,
required this.priceLocale,
required this.numberOfPeriods,
required this.paymentMode,
required this.subscriptionPeriod});
/// Constructing an instance from a map from the Objective-C layer.
///
/// This method should only be used with `map` values returned by [SKProductWrapper.fromJson].
factory SKProductDiscountWrapper.fromJson(Map<String, dynamic> map) {
return _$SKProductDiscountWrapperFromJson(map);
}
/// The discounted price, in the currency that is defined in [priceLocale].
@JsonKey(defaultValue: '')
final String price;
/// Includes locale information about the price, e.g. `$` as the currency symbol for US locale.
final SKPriceLocaleWrapper priceLocale;
/// The object represent the discount period length.
///
/// The value must be >= 0 if the object is valid.
@JsonKey(defaultValue: 0)
final int numberOfPeriods;
/// The object indicates how the discount price is charged.
@SKProductDiscountPaymentModeConverter()
final SKProductDiscountPaymentMode paymentMode;
/// The object represents the duration of single subscription period for the discount.
///
/// The [subscriptionPeriod] of the discount is independent of the product's [subscriptionPeriod],
/// and their units and duration do not have to be matched.
final SKProductSubscriptionPeriodWrapper subscriptionPeriod;
@override
bool operator ==(Object other) {
if (identical(other, this)) {
return true;
}
if (other.runtimeType != runtimeType) {
return false;
}
final SKProductDiscountWrapper typedOther =
other as SKProductDiscountWrapper;
return typedOther.price == price &&
typedOther.priceLocale == priceLocale &&
typedOther.numberOfPeriods == numberOfPeriods &&
typedOther.paymentMode == paymentMode &&
typedOther.subscriptionPeriod == subscriptionPeriod;
}
@override
int get hashCode => hashValues(this.price, this.priceLocale,
this.numberOfPeriods, this.paymentMode, this.subscriptionPeriod);
}
/// Dart wrapper around StoreKit's [SKProduct](https://developer.apple.com/documentation/storekit/skproduct?language=objc).
///
/// A list of [SKProductWrapper] is returned in the [SKRequestMaker.startProductRequest] method, and
/// should be stored for use when making a payment.
@JsonSerializable()
class SKProductWrapper {
/// Creates an [SKProductWrapper] with the given product details.
SKProductWrapper({
required this.productIdentifier,
required this.localizedTitle,
required this.localizedDescription,
required this.priceLocale,
this.subscriptionGroupIdentifier,
required this.price,
this.subscriptionPeriod,
this.introductoryPrice,
this.discounts = const <SKProductDiscountWrapper>[],
});
/// Constructing an instance from a map from the Objective-C layer.
///
/// This method should only be used with `map` values returned by [SkProductResponseWrapper.fromJson].
factory SKProductWrapper.fromJson(Map<String, dynamic> map) {
return _$SKProductWrapperFromJson(map);
}
/// The unique identifier of the product.
@JsonKey(defaultValue: '')
final String productIdentifier;
/// The localizedTitle of the product.
///
/// It is localized based on the current locale.
@JsonKey(defaultValue: '')
final String localizedTitle;
/// The localized description of the product.
///
/// It is localized based on the current locale.
@JsonKey(defaultValue: '')
final String localizedDescription;
/// Includes locale information about the price, e.g. `$` as the currency symbol for US locale.
final SKPriceLocaleWrapper priceLocale;
/// The subscription group identifier.
///
/// If the product is not a subscription, the value is `null`.
///
/// A subscription group is a collection of subscription products.
/// Check [SubscriptionGroup](https://developer.apple.com/app-store/subscriptions/) for more details about subscription group.
final String? subscriptionGroupIdentifier;
/// The price of the product, in the currency that is defined in [priceLocale].
@JsonKey(defaultValue: '')
final String price;
/// The object represents the subscription period of the product.
///
/// Can be [null] is the product is not a subscription.
final SKProductSubscriptionPeriodWrapper? subscriptionPeriod;
/// The object represents the duration of single subscription period.
///
/// This is only available if you set up the introductory price in the App Store Connect, otherwise the value is `null`.
/// Programmer is also responsible to determine if the user is eligible to receive it. See https://developer.apple.com/documentation/storekit/in-app_purchase/offering_introductory_pricing_in_your_app?language=objc
/// for more details.
/// The [subscriptionPeriod] of the discount is independent of the product's [subscriptionPeriod],
/// and their units and duration do not have to be matched.
final SKProductDiscountWrapper? introductoryPrice;
/// An array of subscription offers available for the auto-renewable subscription (available on iOS 12.2 and higher).
///
/// This property lists all promotional offers set up in App Store Connect. If
/// no promotional offers have been set up, this field returns an empty list.
/// Each [subscriptionPeriod] of individual discounts are independent of the
/// product's [subscriptionPeriod] and their units and duration do not have to
/// be matched.
@JsonKey(defaultValue: <SKProductDiscountWrapper>[])
final List<SKProductDiscountWrapper> discounts;
@override
bool operator ==(Object other) {
if (identical(other, this)) {
return true;
}
if (other.runtimeType != runtimeType) {
return false;
}
final SKProductWrapper typedOther = other as SKProductWrapper;
return typedOther.productIdentifier == productIdentifier &&
typedOther.localizedTitle == localizedTitle &&
typedOther.localizedDescription == localizedDescription &&
typedOther.priceLocale == priceLocale &&
typedOther.subscriptionGroupIdentifier == subscriptionGroupIdentifier &&
typedOther.price == price &&
typedOther.subscriptionPeriod == subscriptionPeriod &&
typedOther.introductoryPrice == introductoryPrice &&
DeepCollectionEquality().equals(typedOther.discounts, discounts);
}
@override
int get hashCode => hashValues(
this.productIdentifier,
this.localizedTitle,
this.localizedDescription,
this.priceLocale,
this.subscriptionGroupIdentifier,
this.price,
this.subscriptionPeriod,
this.introductoryPrice,
this.discounts);
}
/// Object that indicates the locale of the price
///
/// It is a thin wrapper of [NSLocale](https://developer.apple.com/documentation/foundation/nslocale?language=objc).
// TODO(cyanglaz): NSLocale is a complex object, want to see the actual need of getting this expanded.
// Matching android to only get the currencySymbol for now.
// https://github.com/flutter/flutter/issues/26610
@JsonSerializable()
class SKPriceLocaleWrapper {
/// Creates a new price locale for `currencySymbol` and `currencyCode`.
SKPriceLocaleWrapper({
required this.currencySymbol,
required this.currencyCode,
required this.countryCode,
});
/// Constructing an instance from a map from the Objective-C layer.
///
/// This method should only be used with `map` values returned by [SKProductWrapper.fromJson] and [SKProductDiscountWrapper.fromJson].
factory SKPriceLocaleWrapper.fromJson(Map<String, dynamic>? map) {
if (map == null) {
return SKPriceLocaleWrapper(
currencyCode: '', currencySymbol: '', countryCode: '');
}
return _$SKPriceLocaleWrapperFromJson(map);
}
///The currency symbol for the locale, e.g. $ for US locale.
@JsonKey(defaultValue: '')
final String currencySymbol;
///The currency code for the locale, e.g. USD for US locale.
@JsonKey(defaultValue: '')
final String currencyCode;
///The country code for the locale, e.g. US for US locale.
@JsonKey(defaultValue: '')
final String countryCode;
@override
bool operator ==(Object other) {
if (identical(other, this)) {
return true;
}
if (other.runtimeType != runtimeType) {
return false;
}
final SKPriceLocaleWrapper typedOther = other as SKPriceLocaleWrapper;
return typedOther.currencySymbol == currencySymbol &&
typedOther.currencyCode == currencyCode;
}
@override
int get hashCode => hashValues(this.currencySymbol, this.currencyCode);
}