├── fastlane ├── Appfile ├── report.xml ├── README.md └── Fastfile ├── ios ├── Assets │ └── .gitkeep ├── Classes │ ├── QonversionPlugin.h │ ├── Extensions.swift │ ├── QonversionPlugin.m │ ├── BaseEventStreamHandler.swift │ ├── FlutterError+Custom.swift │ └── BaseListenerWrapper.swift ├── .gitignore └── qonversion_flutter.podspec ├── example ├── android │ ├── settings_aar.gradle │ ├── .gitignore │ ├── app │ │ ├── src │ │ │ ├── main │ │ │ │ ├── res │ │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── drawable │ │ │ │ │ │ └── launch_background.xml │ │ │ │ │ └── values │ │ │ │ │ │ └── styles.xml │ │ │ │ ├── kotlin │ │ │ │ │ └── io │ │ │ │ │ │ └── qonversion │ │ │ │ │ │ └── sampleapp │ │ │ │ │ │ └── MainActivity.kt │ │ │ │ └── AndroidManifest.xml │ │ │ ├── debug │ │ │ │ └── AndroidManifest.xml │ │ │ └── profile │ │ │ │ └── AndroidManifest.xml │ │ ├── google-services.json │ │ └── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ ├── build.gradle │ └── settings.gradle ├── ios │ ├── Flutter │ │ ├── .last_build_id │ │ ├── Debug.xcconfig │ │ ├── Release.xcconfig │ │ ├── ephemeral │ │ │ ├── flutter_lldbinit │ │ │ └── flutter_lldb_helper.py │ │ └── AppFrameworkInfo.plist │ ├── Runner │ │ ├── Runner-Bridging-Header.h │ │ ├── Assets.xcassets │ │ │ ├── LaunchImage.imageset │ │ │ │ ├── LaunchImage.png │ │ │ │ ├── LaunchImage@2x.png │ │ │ │ ├── LaunchImage@3x.png │ │ │ │ ├── README.md │ │ │ │ └── Contents.json │ │ │ └── AppIcon.appiconset │ │ │ │ ├── Icon-App-20x20@1x.png │ │ │ │ ├── Icon-App-20x20@2x.png │ │ │ │ ├── Icon-App-20x20@3x.png │ │ │ │ ├── Icon-App-29x29@1x.png │ │ │ │ ├── Icon-App-29x29@2x.png │ │ │ │ ├── Icon-App-29x29@3x.png │ │ │ │ ├── Icon-App-40x40@1x.png │ │ │ │ ├── Icon-App-40x40@2x.png │ │ │ │ ├── Icon-App-40x40@3x.png │ │ │ │ ├── Icon-App-60x60@2x.png │ │ │ │ ├── Icon-App-60x60@3x.png │ │ │ │ ├── Icon-App-76x76@1x.png │ │ │ │ ├── Icon-App-76x76@2x.png │ │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ │ ├── Icon-App-83.5x83.5@2x.png │ │ │ │ └── Contents.json │ │ ├── Runner.entitlements │ │ ├── AppDelegate.swift │ │ ├── Base.lproj │ │ │ ├── Main.storyboard │ │ │ └── LaunchScreen.storyboard │ │ └── Info.plist │ ├── Runner.xcodeproj │ │ ├── project.xcworkspace │ │ │ └── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ ├── .gitignore │ ├── GoogleService-Info.plist │ └── Podfile ├── web │ ├── icons │ │ ├── Icon-192.png │ │ └── Icon-512.png │ ├── manifest.json │ └── index.html ├── .metadata ├── pubspec.yaml ├── README.md ├── lib │ ├── main.dart │ └── params_view.dart └── .gitignore ├── android ├── settings.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── .gitignore ├── gradle.properties ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ └── kotlin │ │ └── com │ │ └── qonversion │ │ └── flutter │ │ └── sdk │ │ └── qonversion_flutter_sdk │ │ ├── BaseEventStreamHandler.kt │ │ ├── FlutterResult+CustomErrors.kt │ │ ├── BaseListenerWrapper.kt │ │ └── extenstions.kt ├── build.gradle └── gradlew.bat ├── lib ├── src │ ├── dto │ │ ├── environment.dart │ │ ├── launch_mode.dart │ │ ├── attribution_provider.dart │ │ ├── entitlements_cache_lifetime.dart │ │ ├── transaction_environment.dart │ │ ├── transaction_ownership_type.dart │ │ ├── entitlement_grant_type.dart │ │ ├── experiment_group_type.dart │ │ ├── remote_configuration_assignment_type.dart │ │ ├── entitlement_source.dart │ │ ├── user.g.dart │ │ ├── user_property_key.dart │ │ ├── user.dart │ │ ├── user_property.g.dart │ │ ├── experiment.g.dart │ │ ├── remote_configuration_source_type.dart │ │ ├── transaction_type.dart │ │ ├── user_properties.g.dart │ │ ├── remote_config_list.g.dart │ │ ├── remote_config.g.dart │ │ ├── store_product │ │ │ ├── product_inapp_details.g.dart │ │ │ ├── product_inapp_details.dart │ │ │ ├── product_installment_plan_details.g.dart │ │ │ ├── product_price.g.dart │ │ │ ├── product_installment_plan_details.dart │ │ │ ├── product_price.dart │ │ │ ├── product_offer_details.g.dart │ │ │ ├── product_pricing_phase.g.dart │ │ │ ├── product_store_details.g.dart │ │ │ ├── product_offer_details.dart │ │ │ ├── product_pricing_phase.dart │ │ │ └── product_store_details.dart │ │ ├── qonversion_exception.dart │ │ ├── product_type.dart │ │ ├── experiment.dart │ │ ├── eligibility.dart │ │ ├── purchase_options.dart │ │ ├── entitlement_renew_state.dart │ │ ├── promotional_offer.dart │ │ ├── promotional_offer.g.dart │ │ ├── eligibility.g.dart │ │ ├── experiment_group.g.dart │ │ ├── purchase_exception.dart │ │ ├── remote_config.dart │ │ ├── user_property.dart │ │ ├── sk_product │ │ │ ├── subscription_period_unit.dart │ │ │ ├── discount_payment_mode.dart │ │ │ ├── sk_payment_discount.g.dart │ │ │ ├── subscription_period.g.dart │ │ │ ├── sk_product_discount.g.dart │ │ │ ├── sk_payment_discount.dart │ │ │ ├── subscription_period.dart │ │ │ ├── sk_product.g.dart │ │ │ └── sk_product_discount.dart │ │ ├── experiment_group.dart │ │ ├── purchase_model.dart │ │ ├── qonversion_error.dart │ │ ├── remote_config_list.dart │ │ ├── offerings.g.dart │ │ ├── purchase_update_policy.dart │ │ ├── subscription_period.dart │ │ ├── subscription_period.g.dart │ │ ├── remote_configuration_source.dart │ │ ├── purchase_update_model.dart │ │ ├── offerings.dart │ │ ├── remote_configuration_source.g.dart │ │ ├── product.g.dart │ │ ├── transaction.g.dart │ │ ├── entitlement.g.dart │ │ ├── presentation_config.dart │ │ ├── transaction.dart │ │ ├── qonversion_error.g.dart │ │ ├── user_properties.dart │ │ ├── purchase_options_builder.dart │ │ ├── nocodes_events.dart │ │ └── entitlement.dart │ ├── internal │ │ ├── utils │ │ │ └── string.dart │ │ └── constants.dart │ ├── nocodes_config.dart │ ├── qonversion_config.dart │ ├── nocodes_config_builder.dart │ ├── qonversion_config_builder.dart │ └── nocodes.dart └── qonversion_flutter.dart ├── .gitignore ├── macos ├── Classes │ ├── QonversionPlugin.h │ ├── Extensions.swift │ ├── QonversionPlugin.m │ ├── BaseEventStreamHandler.swift │ ├── FlutterError+Custom.swift │ └── BaseListenerWrapper.swift └── qonversion_flutter.podspec ├── .github └── workflows │ ├── stale_issues.yml │ ├── release_pull_requests.yml │ ├── manual_minor_prerelease.yml │ ├── manual_patch_prerelease.yml │ ├── upgrade_sandwich.yml │ ├── prerelease_github.yml │ ├── checks.yml │ └── publish.yml ├── .metadata ├── test └── qonversion.dart ├── pubspec.yaml ├── LICENSE └── qonversion_flutter_sdk.iml /fastlane/Appfile: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ios/Assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /example/android/settings_aar.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /example/ios/Flutter/.last_build_id: -------------------------------------------------------------------------------- 1 | fcf7455bc4abf953c09f92112449eb86 -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'qonversion_flutter_sdk' 2 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /lib/src/dto/environment.dart: -------------------------------------------------------------------------------- 1 | enum QEnvironment { 2 | sandbox, 3 | production, 4 | } 5 | -------------------------------------------------------------------------------- /lib/src/dto/launch_mode.dart: -------------------------------------------------------------------------------- 1 | enum QLaunchMode { 2 | analytics, 3 | subscriptionManagement, 4 | } 5 | -------------------------------------------------------------------------------- /example/web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qonversion/flutter-sdk/HEAD/example/web/icons/Icon-192.png -------------------------------------------------------------------------------- /example/web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qonversion/flutter-sdk/HEAD/example/web/icons/Icon-512.png -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qonversion/flutter-sdk/HEAD/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | android.enableR8=true 5 | -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /lib/src/internal/utils/string.dart: -------------------------------------------------------------------------------- 1 | class StringUtils { 2 | static String capitalize(String s) => s[0].toUpperCase() + s.substring(1); 3 | } 4 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qonversion/flutter-sdk/HEAD/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qonversion/flutter-sdk/HEAD/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/ios/Flutter/ephemeral/flutter_lldbinit: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | command script import --relative-to-command-file flutter_lldb_helper.py 6 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qonversion/flutter-sdk/HEAD/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qonversion/flutter-sdk/HEAD/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /fastlane/report.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qonversion/flutter-sdk/HEAD/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx4096M -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /lib/src/dto/attribution_provider.dart: -------------------------------------------------------------------------------- 1 | enum QAttributionProvider { 2 | appsFlyer, 3 | branch, 4 | adjust, 5 | appleSearchAds, // ios only 6 | appleAdServices, // ios only 7 | } 8 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qonversion/flutter-sdk/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .dart_tool/ 3 | 4 | .packages 5 | .pub/ 6 | 7 | build/ 8 | .idea 9 | 10 | lib/*.jar 11 | 12 | # Local configuration files 13 | local.properties 14 | **/local.properties -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qonversion/flutter-sdk/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qonversion/flutter-sdk/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qonversion/flutter-sdk/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qonversion/flutter-sdk/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qonversion/flutter-sdk/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qonversion/flutter-sdk/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qonversion/flutter-sdk/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qonversion/flutter-sdk/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qonversion/flutter-sdk/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qonversion/flutter-sdk/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qonversion/flutter-sdk/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qonversion/flutter-sdk/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qonversion/flutter-sdk/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qonversion/flutter-sdk/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qonversion/flutter-sdk/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qonversion/flutter-sdk/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qonversion/flutter-sdk/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ios/Classes/QonversionPlugin.h: -------------------------------------------------------------------------------- 1 | #if TARGET_OS_OSX 2 | #import 3 | #else 4 | #import 5 | #endif 6 | 7 | @interface QonversionPlugin : NSObject 8 | @end 9 | -------------------------------------------------------------------------------- /lib/src/dto/entitlements_cache_lifetime.dart: -------------------------------------------------------------------------------- 1 | enum QEntitlementsCacheLifetime { 2 | week, 3 | twoWeeks, 4 | month, 5 | twoMonths, 6 | threeMonths, 7 | sixMonths, 8 | year, 9 | unlimited, 10 | } 11 | -------------------------------------------------------------------------------- /macos/Classes/QonversionPlugin.h: -------------------------------------------------------------------------------- 1 | #if TARGET_OS_OSX 2 | #import 3 | #else 4 | #import 5 | #endif 6 | 7 | @interface QonversionPlugin : NSObject 8 | @end 9 | -------------------------------------------------------------------------------- /.github/workflows/stale_issues.yml: -------------------------------------------------------------------------------- 1 | name: Close stale issues 2 | on: 3 | schedule: 4 | - cron: '30 1 * * *' 5 | 6 | jobs: 7 | stale_issues: 8 | uses: qonversion/shared-sdk-workflows/.github/workflows/stale_issues.yml@main -------------------------------------------------------------------------------- /lib/src/nocodes_config.dart: -------------------------------------------------------------------------------- 1 | /// Configuration for No-Codes initialization 2 | class NoCodesConfig { 3 | final String projectKey; 4 | final String? proxyUrl; 5 | 6 | const NoCodesConfig(this.projectKey, {this.proxyUrl}); 7 | } -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /lib/src/dto/transaction_environment.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | enum QTransactionEnvironment { 4 | @JsonValue('Production') 5 | production, 6 | 7 | @JsonValue('Sandbox') 8 | sandbox, 9 | } 10 | -------------------------------------------------------------------------------- /lib/src/dto/transaction_ownership_type.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | enum QTransactionOwnershipType { 4 | @JsonValue('Owner') 5 | owner, 6 | 7 | @JsonValue('FamilySharing') 8 | familySharing, 9 | } 10 | -------------------------------------------------------------------------------- /.github/workflows/release_pull_requests.yml: -------------------------------------------------------------------------------- 1 | name: Release pull requests from dev by tag 2 | on: 3 | push: 4 | tags: 5 | - prerelease/* 6 | 7 | jobs: 8 | handle_prerelease: 9 | uses: qonversion/shared-sdk-workflows/.github/workflows/prerelease_handling.yml@main 10 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Jan 11 15:14:08 MSK 2024 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | aps-environment 6 | development 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /lib/src/dto/entitlement_grant_type.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | enum QEntitlementGrantType { 4 | @JsonValue('Purchase') 5 | purchase, 6 | 7 | @JsonValue('FamilySharing') 8 | familySharing, 9 | 10 | @JsonValue('OfferCode') 11 | offerCode, 12 | 13 | @JsonValue('Manual') 14 | manual, 15 | } 16 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: fabeb2a16f1d008ab8230f450c49141d35669798 8 | channel: beta 9 | 10 | project_type: plugin 11 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /example/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: fabeb2a16f1d008ab8230f450c49141d35669798 8 | channel: beta 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /lib/src/dto/experiment_group_type.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | enum QExperimentGroupType { 4 | /// Unknown group 5 | @JsonValue('unknown') 6 | unknown, 7 | 8 | /// Treatment group 9 | @JsonValue('treatment') 10 | treatment, 11 | 12 | /// Control group 13 | @JsonValue('control') 14 | control, 15 | } 16 | -------------------------------------------------------------------------------- /.github/workflows/manual_minor_prerelease.yml: -------------------------------------------------------------------------------- 1 | name: Manual Minor Prerelease 2 | 3 | on: 4 | workflow_dispatch 5 | 6 | jobs: 7 | patch-minor: 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v2 12 | with: 13 | ref: develop 14 | 15 | - name: Minor 16 | run: | 17 | fastlane minor 18 | -------------------------------------------------------------------------------- /lib/src/dto/remote_configuration_assignment_type.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | enum QRemoteConfigurationAssignmentType { 4 | /// Unknown assignment type 5 | @JsonValue('unknown') 6 | unknown, 7 | 8 | /// Auto 9 | @JsonValue('auto') 10 | auto, 11 | 12 | /// Manual 13 | @JsonValue('manual') 14 | manual, 15 | } 16 | -------------------------------------------------------------------------------- /.github/workflows/manual_patch_prerelease.yml: -------------------------------------------------------------------------------- 1 | name: Manual Patch Prerelease 2 | 3 | on: 4 | workflow_dispatch 5 | 6 | jobs: 7 | patch-prerelease: 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v2 12 | with: 13 | ref: develop 14 | 15 | - name: Patch 16 | run: | 17 | fastlane patch 18 | -------------------------------------------------------------------------------- /lib/src/dto/entitlement_source.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | enum QEntitlementSource { 4 | @JsonValue("Unknown") 5 | unknown, 6 | 7 | @JsonValue("AppStore") 8 | appStore, 9 | 10 | @JsonValue("PlayStore") 11 | playStore, 12 | 13 | @JsonValue("Stripe") 14 | stripe, 15 | 16 | @JsonValue("Manual") 17 | manual, 18 | } 19 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | allprojects { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | } 6 | } 7 | 8 | rootProject.buildDir = '../build' 9 | subprojects { 10 | project.buildDir = "${rootProject.buildDir}/${project.name}" 11 | } 12 | subprojects { 13 | project.evaluationDependsOn(':app') 14 | } 15 | 16 | tasks.register("clean", Delete) { 17 | delete rootProject.buildDir 18 | } 19 | 20 | -------------------------------------------------------------------------------- /.github/workflows/upgrade_sandwich.yml: -------------------------------------------------------------------------------- 1 | name: Upgrade Sandwich 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | sandwich_version: 6 | description: 'Sandwich version' 7 | required: true 8 | default: '0.0.0' 9 | 10 | jobs: 11 | upgrade: 12 | uses: qonversion/shared-sdk-workflows/.github/workflows/upgrade_sandwich.yml@main 13 | with: 14 | sandwich_version: ${{ github.event.inputs.sandwich_version }} 15 | -------------------------------------------------------------------------------- /lib/src/dto/user.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'user.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | QUser _$QUserFromJson(Map json) => QUser( 10 | json['qonversionId'] as String, 11 | json['identityId'] as String?, 12 | ); 13 | -------------------------------------------------------------------------------- /lib/src/dto/user_property_key.dart: -------------------------------------------------------------------------------- 1 | enum QUserPropertyKey { 2 | email, 3 | name, 4 | kochavaDeviceId, 5 | appsFlyerUserId, 6 | adjustAdId, 7 | customUserId, 8 | facebookAttribution, // Android only 9 | firebaseAppInstanceId, 10 | appSetId, // Android only 11 | advertisingId, // iOS only 12 | appMetricaDeviceId, 13 | appMetricaUserProfileId, 14 | pushWooshHwId, 15 | pushWooshUserId, 16 | tenjinAnalyticsInstallationId, 17 | custom, 18 | } 19 | -------------------------------------------------------------------------------- /lib/src/dto/user.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part 'user.g.dart'; 4 | 5 | @JsonSerializable(createToJson: false) 6 | class QUser { 7 | @JsonKey(name: "qonversionId") 8 | final String qonversionId; 9 | 10 | @JsonKey(name: "identityId") 11 | final String? identityId; 12 | 13 | QUser(this.qonversionId, this.identityId); 14 | 15 | factory QUser.fromJson(Map json) => 16 | _$QUserFromJson(json); 17 | } 18 | -------------------------------------------------------------------------------- /lib/src/dto/user_property.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'user_property.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | QUserProperty _$QUserPropertyFromJson(Map json) => 10 | QUserProperty( 11 | json['key'] as String, 12 | json['value'] as String, 13 | ); 14 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | import Qonversion 4 | 5 | @main 6 | @objc class AppDelegate: FlutterAppDelegate { 7 | override func application( 8 | _ application: UIApplication, 9 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 10 | ) -> Bool { 11 | GeneratedPluginRegistrant.register(with: self) 12 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /ios/Classes/Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions.swift 3 | // qonversion_flutter 4 | // 5 | // Created by Kamo Spertsyan on 05.09.2022. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Dictionary { 11 | func toJson() -> String? { 12 | guard let jsonData = try? JSONSerialization.data(withJSONObject: self, 13 | options: []) else { 14 | return nil 15 | } 16 | 17 | return String(data: jsonData, encoding: .utf8) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/src/qonversion_config.dart: -------------------------------------------------------------------------------- 1 | import '../qonversion_flutter.dart'; 2 | 3 | class QonversionConfig { 4 | final String projectKey; 5 | 6 | final QLaunchMode launchMode; 7 | 8 | final QEnvironment environment; 9 | 10 | final QEntitlementsCacheLifetime entitlementsCacheLifetime; 11 | 12 | final String? proxyUrl; 13 | 14 | final bool kidsMode; 15 | 16 | QonversionConfig(this.projectKey, this.launchMode, this.environment, this.entitlementsCacheLifetime, this.proxyUrl, this.kidsMode); 17 | } 18 | -------------------------------------------------------------------------------- /macos/Classes/Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions.swift 3 | // qonversion_flutter 4 | // 5 | // Created by Kamo Spertsyan on 05.09.2022. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Dictionary { 11 | func toJson() -> String? { 12 | guard let jsonData = try? JSONSerialization.data(withJSONObject: self, 13 | options: []) else { 14 | return nil 15 | } 16 | 17 | return String(data: jsonData, encoding: .utf8) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/io/qonversion/sampleapp/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package io.qonversion.sampleapp 2 | 3 | import androidx.annotation.NonNull; 4 | import io.flutter.embedding.android.FlutterActivity 5 | import io.flutter.embedding.engine.FlutterEngine 6 | import io.flutter.plugins.GeneratedPluginRegistrant 7 | 8 | class MainActivity: FlutterActivity() { 9 | override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { 10 | GeneratedPluginRegistrant.registerWith(flutterEngine) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /.github/workflows/prerelease_github.yml: -------------------------------------------------------------------------------- 1 | name: Pre-release Github 2 | 3 | on: 4 | push: 5 | branches: 6 | - "main" 7 | 8 | jobs: 9 | pre-release: 10 | runs-on: macos-latest 11 | 12 | steps: 13 | - uses: "marvinpinto/action-automatic-releases@latest" 14 | with: 15 | repo_token: "${{ secrets.GITHUB_TOKEN }}" 16 | automatic_release_tag: "latest" 17 | prerelease: true 18 | title: "Development Build" 19 | files: | 20 | LICENSE.txt 21 | *.jar 22 | -------------------------------------------------------------------------------- /lib/src/dto/experiment.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'experiment.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | QExperiment _$QExperimentFromJson(Map json) => QExperiment( 10 | json['id'] as String, 11 | json['name'] as String, 12 | QExperimentGroup.fromJson(json['group'] as Map), 13 | ); 14 | -------------------------------------------------------------------------------- /lib/src/dto/remote_configuration_source_type.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | enum QRemoteConfigurationSourceType { 4 | /// Unknown source type 5 | @JsonValue('unknown') 6 | unknown, 7 | 8 | /// Treatment group 9 | @JsonValue('experiment_treatment_group') 10 | experimentTreatmentGroup, 11 | 12 | /// Control group 13 | @JsonValue('experiment_control_group') 14 | experimentControlGroup, 15 | 16 | /// Remote configuration 17 | @JsonValue('remote_configuration') 18 | remoteConfiguration, 19 | } 20 | -------------------------------------------------------------------------------- /android/src/main/kotlin/com/qonversion/flutter/sdk/qonversion_flutter_sdk/BaseEventStreamHandler.kt: -------------------------------------------------------------------------------- 1 | package com.qonversion.flutter.sdk.qonversion_flutter_sdk 2 | 3 | import io.flutter.plugin.common.EventChannel 4 | 5 | class BaseEventStreamHandler : EventChannel.StreamHandler { 6 | var eventSink: EventChannel.EventSink? = null 7 | 8 | override fun onListen(arguments: Any?, events: EventChannel.EventSink?) { 9 | eventSink = events 10 | } 11 | 12 | override fun onCancel(arguments: Any?) { 13 | eventSink = null 14 | } 15 | } -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: qonversion_example 2 | description: Demonstrates how to use the qonversion plugin. 3 | 4 | publish_to: 'none' 5 | 6 | version: 1.0.0 7 | 8 | environment: 9 | sdk: ">=3.0.0 <4.0.0" 10 | flutter: ">=3.0.0" 11 | 12 | dependencies: 13 | flutter: 14 | sdk: flutter 15 | 16 | dependency_overrides: 17 | firebase_core_platform_interface: 4.5.1 18 | 19 | dev_dependencies: 20 | flutter_test: 21 | sdk: flutter 22 | 23 | qonversion_flutter: 24 | path: ../ 25 | 26 | flutter: 27 | uses-material-design: true 28 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vagrant/ 3 | .sconsign.dblite 4 | .svn/ 5 | 6 | .DS_Store 7 | *.swp 8 | profile 9 | 10 | DerivedData/ 11 | build/ 12 | GeneratedPluginRegistrant.h 13 | GeneratedPluginRegistrant.m 14 | 15 | .generated/ 16 | 17 | *.pbxuser 18 | *.mode1v3 19 | *.mode2v3 20 | *.perspectivev3 21 | 22 | !default.pbxuser 23 | !default.mode1v3 24 | !default.mode2v3 25 | !default.perspectivev3 26 | 27 | xcuserdata 28 | 29 | *.moved-aside 30 | 31 | *.pyc 32 | *sync/ 33 | Icon? 34 | .tags* 35 | 36 | /Flutter/Generated.xcconfig 37 | /Flutter/flutter_export_environment.sh -------------------------------------------------------------------------------- /lib/src/dto/transaction_type.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | enum QTransactionType { 4 | @JsonValue('Unknown') 5 | unknown, 6 | 7 | @JsonValue('SubscriptionStarted') 8 | subscriptionStarted, 9 | 10 | @JsonValue('SubscriptionRenewed') 11 | subscriptionRenewed, 12 | 13 | @JsonValue('TrialStarted') 14 | trialStrated, 15 | 16 | @JsonValue('IntroStarted') 17 | introStarted, 18 | 19 | @JsonValue('IntroRenewed') 20 | introRenewed, 21 | 22 | @JsonValue('NonConsumablePurchase') 23 | nonConsumablePurchase, 24 | } 25 | -------------------------------------------------------------------------------- /lib/src/dto/user_properties.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'user_properties.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | QUserProperties _$QUserPropertiesFromJson(Map json) => 10 | QUserProperties( 11 | (json['properties'] as List) 12 | .map((e) => QUserProperty.fromJson(e as Map)) 13 | .toList(), 14 | ); 15 | -------------------------------------------------------------------------------- /lib/src/dto/remote_config_list.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'remote_config_list.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | QRemoteConfigList _$QRemoteConfigListFromJson(Map json) => 10 | QRemoteConfigList( 11 | (json['remoteConfigs'] as List) 12 | .map((e) => QRemoteConfig.fromJson(e as Map)) 13 | .toList(), 14 | ); 15 | -------------------------------------------------------------------------------- /android/src/main/kotlin/com/qonversion/flutter/sdk/qonversion_flutter_sdk/FlutterResult+CustomErrors.kt: -------------------------------------------------------------------------------- 1 | package com.qonversion.flutter.sdk.qonversion_flutter_sdk 2 | 3 | import io.flutter.plugin.common.MethodChannel 4 | import io.qonversion.sandwich.SandwichError 5 | 6 | fun MethodChannel.Result.noNecessaryDataError() { 7 | return error( 8 | "NoNecessaryDataError", 9 | "Could not find necessary arguments", 10 | "Make sure you pass correct call arguments" 11 | ) 12 | } 13 | 14 | fun MethodChannel.Result.sandwichError(error: SandwichError) { 15 | return error(error.code, error.description, error.additionalMessage) 16 | } 17 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # qonversion_flutter_sdk_example 2 | 3 | Demonstrates how to use the qonversion_flutter_sdk plugin. 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Flutter application. 8 | 9 | A few resources to get you started if this is your first Flutter project: 10 | 11 | - [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) 13 | 14 | For help getting started with Flutter, view our 15 | [online documentation](https://flutter.dev/docs), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /.github/workflows/checks.yml: -------------------------------------------------------------------------------- 1 | name: Checks 2 | on: 3 | pull_request: 4 | types: [opened, synchronize, reopened] 5 | 6 | jobs: 7 | lint: 8 | runs-on: macos-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v2 12 | name: Checkout 13 | 14 | - uses: actions/setup-java@v1 15 | name: Setup Java 16 | with: 17 | java-version: '12.x' 18 | 19 | - uses: subosito/flutter-action@v1 20 | name: Setup Flutter 21 | with: 22 | flutter-version: '3.32.4' 23 | channel: 'stable' 24 | - run: flutter pub get 25 | - name: Validation 26 | run: flutter pub publish --dry-run 27 | -------------------------------------------------------------------------------- /ios/Classes/QonversionPlugin.m: -------------------------------------------------------------------------------- 1 | #import "QonversionPlugin.h" 2 | #if __has_include() 3 | #import 4 | #else 5 | // Support project import fallback if the generated compatibility header 6 | // is not copied when this plugin is created as a library. 7 | // https://forums.swift.org/t/swift-static-libraries-dont-copy-generated-objective-c-header/19816 8 | #import "qonversion_flutter-Swift.h" 9 | #endif 10 | 11 | @implementation QonversionPlugin 12 | + (void)registerWithRegistrar:(NSObject*)registrar { 13 | [SwiftQonversionPlugin registerWithRegistrar:registrar]; 14 | } 15 | @end 16 | -------------------------------------------------------------------------------- /macos/Classes/QonversionPlugin.m: -------------------------------------------------------------------------------- 1 | #import "QonversionPlugin.h" 2 | #if __has_include() 3 | #import 4 | #else 5 | // Support project import fallback if the generated compatibility header 6 | // is not copied when this plugin is created as a library. 7 | // https://forums.swift.org/t/swift-static-libraries-dont-copy-generated-objective-c-header/19816 8 | #import "qonversion_flutter-Swift.h" 9 | #endif 10 | 11 | @implementation QonversionPlugin 12 | + (void)registerWithRegistrar:(NSObject*)registrar { 13 | [SwiftQonversionPlugin registerWithRegistrar:registrar]; 14 | } 15 | @end 16 | -------------------------------------------------------------------------------- /example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /test/qonversion.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/services.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | 4 | void main() { 5 | const MethodChannel channel = MethodChannel('qonversion_flutter_sdk'); 6 | 7 | TestWidgetsFlutterBinding.ensureInitialized(); 8 | 9 | setUp(() { 10 | TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger 11 | .setMockMethodCallHandler(channel, (MethodCall methodCall) async { 12 | return '42'; 13 | }); 14 | }); 15 | 16 | tearDown(() { 17 | TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger 18 | .setMockMethodCallHandler(channel, null); 19 | }); 20 | 21 | test('test', () async {}); 22 | } 23 | -------------------------------------------------------------------------------- /lib/src/dto/remote_config.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'remote_config.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | QRemoteConfig _$QRemoteConfigFromJson(Map json) => 10 | QRemoteConfig( 11 | json['payload'] as Map, 12 | json['experiment'] == null 13 | ? null 14 | : QExperiment.fromJson(json['experiment'] as Map), 15 | QRemoteConfigurationSource.fromJson( 16 | json['source'] as Map), 17 | ); 18 | -------------------------------------------------------------------------------- /lib/src/dto/store_product/product_inapp_details.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'product_inapp_details.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | QProductInAppDetails _$QProductInAppDetailsFromJson( 10 | Map json) => 11 | QProductInAppDetails( 12 | QMapper.requiredProductPriceFromJson(json['price']), 13 | ); 14 | 15 | Map _$QProductInAppDetailsToJson( 16 | QProductInAppDetails instance) => 17 | { 18 | 'price': instance.price, 19 | }; 20 | -------------------------------------------------------------------------------- /lib/src/dto/qonversion_exception.dart: -------------------------------------------------------------------------------- 1 | /// General Qonversion exception for all non-purchase related errors 2 | class QonversionException implements Exception { 3 | /// Qonversion Error Code 4 | /// 5 | /// See more in [documentation](https://documentation.qonversion.io/docs/handling-errors) 6 | final String code; 7 | 8 | /// Error description 9 | final String message; 10 | 11 | /// Additional error info 12 | final String? details; 13 | 14 | const QonversionException( 15 | this.code, 16 | this.message, 17 | this.details, 18 | ); 19 | 20 | @override 21 | String toString() { 22 | return 'QonversionException.\nCode: $code, Description: $message, Additional Message: $details'; 23 | } 24 | } 25 | 26 | -------------------------------------------------------------------------------- /lib/src/dto/product_type.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | enum QProductType { 4 | /// Provides access to content on a recurring basis with a free trial offer 5 | @JsonValue('Trial') 6 | trial, 7 | 8 | /// Provides access to content on a recurring basis with an introductory price offer 9 | /// Currently works for Android only. iOS support will be added soon. 10 | @JsonValue('Intro') 11 | intro, 12 | 13 | /// Provides access to content on a recurring basis 14 | @JsonValue('Subscription') 15 | subscription, 16 | 17 | /// Content that users can purchase with a single, non-recurring charge 18 | @JsonValue('InApp') 19 | inApp, 20 | 21 | @JsonValue('Unknown') 22 | unknown, 23 | } 24 | -------------------------------------------------------------------------------- /lib/src/dto/experiment.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | import 'experiment_group.dart'; 4 | 5 | part 'experiment.g.dart'; 6 | 7 | @JsonSerializable(createToJson: false) 8 | class QExperiment { 9 | /// Experiment's identifier. 10 | @JsonKey(name: 'id') 11 | final String id; 12 | 13 | /// Experiment's name. 14 | @JsonKey(name: 'name') 15 | final String name; 16 | 17 | /// Experiment's group the user has been assigned to. 18 | @JsonKey(name: 'group') 19 | final QExperimentGroup group; 20 | 21 | const QExperiment( 22 | this.id, 23 | this.name, 24 | this.group, 25 | ); 26 | 27 | factory QExperiment.fromJson(Map json) => 28 | _$QExperimentFromJson(json); 29 | } 30 | -------------------------------------------------------------------------------- /lib/src/dto/eligibility.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part 'eligibility.g.dart'; 4 | 5 | enum QEligibilityStatus { 6 | @JsonValue('unknown') 7 | unknown, 8 | @JsonValue('non_intro_or_trial_product') 9 | nonIntroOrTrialProduct, 10 | @JsonValue('intro_or_trial_ineligible') 11 | ineligible, 12 | @JsonValue('intro_or_trial_eligible') 13 | eligible, 14 | } 15 | 16 | @JsonSerializable(createToJson: false) 17 | class QEligibility { 18 | @JsonKey(name: "status", defaultValue: QEligibilityStatus.unknown) 19 | final QEligibilityStatus status; 20 | 21 | const QEligibility(this.status); 22 | 23 | factory QEligibility.fromJson(Map json) => 24 | _$QEligibilityFromJson(json); 25 | } 26 | -------------------------------------------------------------------------------- /lib/src/dto/purchase_options.dart: -------------------------------------------------------------------------------- 1 | import 'package:qonversion_flutter/qonversion_flutter.dart'; 2 | 3 | /// Purchase options that may be used to modify purchase process. 4 | /// To create an instance, use [QPurchaseOptionsBuilder] class. 5 | class QPurchaseOptions { 6 | final String? offerId; 7 | final bool applyOffer; 8 | final QProduct? oldProduct; 9 | final QPurchaseUpdatePolicy? updatePolicy; 10 | final List? contextKeys; 11 | final int quantity; 12 | final QPromotionalOffer? promotionalOffer; 13 | 14 | QPurchaseOptions( 15 | this.offerId, 16 | this.applyOffer, 17 | this.oldProduct, 18 | this.updatePolicy, 19 | this.contextKeys, 20 | this.quantity, 21 | this.promotionalOffer, 22 | ); 23 | } -------------------------------------------------------------------------------- /example/web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "qonversion_flutter_sdk_example", 3 | "short_name": "qonversion_flutter_sdk_example", 4 | "start_url": ".", 5 | "display": "minimal-ui", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "Demonstrates how to use the qonversion_flutter_sdk plugin.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /ios/Classes/BaseEventStreamHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseEventStreamHandler.swift 3 | // qonversion_flutter 4 | // 5 | // Created by Ilya Virnik on 2/7/21. 6 | // 7 | 8 | #if canImport(FlutterMacOS) 9 | import FlutterMacOS 10 | #else 11 | import Flutter 12 | #endif 13 | 14 | protocol EventStreamHandler: NSObject, FlutterStreamHandler {} 15 | 16 | class BaseEventStreamHandler: NSObject, EventStreamHandler { 17 | var eventSink: FlutterEventSink? 18 | 19 | public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { 20 | eventSink = events 21 | return nil 22 | } 23 | 24 | public func onCancel(withArguments arguments: Any?) -> FlutterError? { 25 | eventSink = nil 26 | return nil 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/src/dto/entitlement_renew_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | enum QEntitlementRenewState { 4 | /// For in-app purchases. 5 | @JsonValue('non_renewable') 6 | nonRenewable, 7 | 8 | /// For in-app purchases. 9 | @JsonValue('unknown') 10 | unknown, 11 | 12 | /// Subscription is active and will renew 13 | @JsonValue('will_renew') 14 | willRenew, 15 | 16 | /// The user canceled the subscription, but the subscription may be active. 17 | /// Check isActive to be sure that the subscription has not expired yet. 18 | @JsonValue('canceled') 19 | canceled, 20 | 21 | /// There was some billing issue. 22 | /// Prompt the user to update the payment method. 23 | @JsonValue('billing_issue') 24 | billingIssue, 25 | } 26 | -------------------------------------------------------------------------------- /macos/Classes/BaseEventStreamHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseEventStreamHandler.swift 3 | // qonversion_flutter 4 | // 5 | // Created by Ilya Virnik on 2/7/21. 6 | // 7 | 8 | #if canImport(FlutterMacOS) 9 | import FlutterMacOS 10 | #else 11 | import Flutter 12 | #endif 13 | 14 | protocol EventStreamHandler: NSObject, FlutterStreamHandler {} 15 | 16 | class BaseEventStreamHandler: NSObject, EventStreamHandler { 17 | var eventSink: FlutterEventSink? 18 | 19 | public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { 20 | eventSink = events 21 | return nil 22 | } 23 | 24 | public func onCancel(withArguments arguments: Any?) -> FlutterError? { 25 | eventSink = nil 26 | return nil 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'home.dart'; 4 | import 'params_view.dart'; 5 | import 'products_view.dart'; 6 | import 'nocodes_view.dart'; 7 | import 'dart:async'; 8 | 9 | Future main() async { 10 | WidgetsFlutterBinding.ensureInitialized(); 11 | 12 | runApp(SampleApp()); 13 | } 14 | 15 | /// Entry point for the example application. 16 | class SampleApp extends StatelessWidget { 17 | @override 18 | Widget build(BuildContext context) { 19 | return MaterialApp( 20 | initialRoute: '/', 21 | routes: { 22 | '/': (_) => HomeView(), 23 | 'products': (_) => ProductsView(), 24 | 'params': (_) => ParamsView(), 25 | 'nocodes': (_) => NoCodesView(), 26 | }, 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/src/nocodes_config_builder.dart: -------------------------------------------------------------------------------- 1 | import 'nocodes_config.dart'; 2 | 3 | /// Builder for No-Codes configuration 4 | class NoCodesConfigBuilder { 5 | final String projectKey; 6 | String? proxyUrl; 7 | 8 | NoCodesConfigBuilder(this.projectKey); 9 | 10 | /// Set proxy URL for No-Codes initialization. 11 | /// 12 | /// [proxyUrl] the proxy URL to use. 13 | /// Returns the builder instance for method chaining. 14 | NoCodesConfigBuilder setProxyUrl(String? proxyUrl) { 15 | this.proxyUrl = proxyUrl; 16 | return this; 17 | } 18 | 19 | /// Generate [NoCodesConfig] instance with all the provided configurations. 20 | /// 21 | /// Returns the complete [NoCodesConfig] instance. 22 | NoCodesConfig build() { 23 | return NoCodesConfig(projectKey, proxyUrl: proxyUrl); 24 | } 25 | } -------------------------------------------------------------------------------- /lib/src/dto/promotional_offer.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | import 'package:qonversion_flutter/qonversion_flutter.dart'; 3 | 4 | import '../internal/mapper.dart'; 5 | 6 | part 'promotional_offer.g.dart'; 7 | 8 | @JsonSerializable() 9 | class QPromotionalOffer { 10 | 11 | @JsonKey(fromJson: QMapper.requiredSkProductDiscountFromJson) 12 | final SKProductDiscount productDiscount; 13 | 14 | @JsonKey(fromJson: QMapper.skPaymentDiscountFromJson) 15 | final SKPaymentDiscount paymentDiscount; 16 | 17 | QPromotionalOffer(this.productDiscount, this.paymentDiscount); 18 | 19 | factory QPromotionalOffer.fromJson(Map json) => 20 | _$QPromotionalOfferFromJson(json); 21 | 22 | Map toJson() => _$QPromotionalOfferToJson(this); 23 | } 24 | -------------------------------------------------------------------------------- /lib/src/dto/promotional_offer.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'promotional_offer.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | QPromotionalOffer _$QPromotionalOfferFromJson(Map json) => 10 | QPromotionalOffer( 11 | QMapper.requiredSkProductDiscountFromJson(json['productDiscount']), 12 | QMapper.skPaymentDiscountFromJson(json['paymentDiscount']), 13 | ); 14 | 15 | Map _$QPromotionalOfferToJson(QPromotionalOffer instance) => 16 | { 17 | 'productDiscount': instance.productDiscount, 18 | 'paymentDiscount': instance.paymentDiscount, 19 | }; 20 | -------------------------------------------------------------------------------- /lib/src/dto/store_product/product_inapp_details.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | import 'package:qonversion_flutter/src/internal/mapper.dart'; 3 | 4 | import './product_price.dart'; 5 | 6 | part 'product_inapp_details.g.dart'; 7 | 8 | /// This class contains all the information about the Google in-app product details. 9 | @JsonSerializable() 10 | class QProductInAppDetails { 11 | /// The price of the in-app product. 12 | @JsonKey(name: 'price', fromJson: QMapper.requiredProductPriceFromJson) 13 | final QProductPrice price; 14 | 15 | const QProductInAppDetails(this.price); 16 | 17 | factory QProductInAppDetails.fromJson(Map json) => 18 | _$QProductInAppDetailsFromJson(json); 19 | 20 | Map toJson() => _$QProductInAppDetailsToJson(this); 21 | } 22 | -------------------------------------------------------------------------------- /lib/src/dto/eligibility.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'eligibility.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | QEligibility _$QEligibilityFromJson(Map json) => QEligibility( 10 | $enumDecodeNullable(_$QEligibilityStatusEnumMap, json['status']) ?? 11 | QEligibilityStatus.unknown, 12 | ); 13 | 14 | const _$QEligibilityStatusEnumMap = { 15 | QEligibilityStatus.unknown: 'unknown', 16 | QEligibilityStatus.nonIntroOrTrialProduct: 'non_intro_or_trial_product', 17 | QEligibilityStatus.ineligible: 'intro_or_trial_ineligible', 18 | QEligibilityStatus.eligible: 'intro_or_trial_eligible', 19 | }; 20 | -------------------------------------------------------------------------------- /lib/src/dto/experiment_group.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'experiment_group.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | QExperimentGroup _$QExperimentGroupFromJson(Map json) => 10 | QExperimentGroup( 11 | json['id'] as String, 12 | json['name'] as String, 13 | $enumDecode(_$QExperimentGroupTypeEnumMap, json['type'], 14 | unknownValue: QExperimentGroupType.unknown), 15 | ); 16 | 17 | const _$QExperimentGroupTypeEnumMap = { 18 | QExperimentGroupType.unknown: 'unknown', 19 | QExperimentGroupType.treatment: 'treatment', 20 | QExperimentGroupType.control: 'control', 21 | }; 22 | -------------------------------------------------------------------------------- /lib/src/dto/purchase_exception.dart: -------------------------------------------------------------------------------- 1 | class QPurchaseException implements Exception { 2 | /// Qonversion Error Code 3 | /// 4 | /// See more in [documentation](https://documentation.qonversion.io/docs/handling-errors) 5 | final String code; 6 | 7 | /// Error description 8 | final String message; 9 | 10 | /// Additional error info 11 | final String? additionalMessage; 12 | 13 | /// `true` if user explicitly cancelled purchasing process. 14 | final bool isUserCancelled; 15 | 16 | const QPurchaseException( 17 | this.code, 18 | this.message, 19 | this.additionalMessage, { 20 | required this.isUserCancelled, 21 | }); 22 | 23 | @override 24 | String toString() { 25 | return 'QPurchaseException.\nCode: $code, Description: $message, Additional Message: $additionalMessage, isUserCancelled: $isUserCancelled'; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /example/android/app/google-services.json: -------------------------------------------------------------------------------- 1 | { 2 | "project_info": { 3 | "project_number": "176235602178", 4 | "project_id": "qonversion-sample-applic-eb8a7", 5 | "storage_bucket": "qonversion-sample-applic-eb8a7.firebasestorage.app" 6 | }, 7 | "client": [ 8 | { 9 | "client_info": { 10 | "mobilesdk_app_id": "1:176235602178:android:b090da6fa0510c7192621b", 11 | "android_client_info": { 12 | "package_name": "io.qonversion.sampleapp" 13 | } 14 | }, 15 | "oauth_client": [], 16 | "api_key": [ 17 | { 18 | "current_key": "AIzaSyA0fYxEeTDdoXZopKU54FtsN75FAcmZE9U" 19 | } 20 | ], 21 | "services": { 22 | "appinvite_service": { 23 | "other_platform_oauth_client": [] 24 | } 25 | } 26 | } 27 | ], 28 | "configuration_version": "1" 29 | } -------------------------------------------------------------------------------- /lib/src/dto/remote_config.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | import 'experiment.dart'; 4 | import 'remote_configuration_source.dart'; 5 | 6 | part 'remote_config.g.dart'; 7 | 8 | @JsonSerializable(createToJson: false) 9 | class QRemoteConfig { 10 | /// JSON payload you have configured using the Qonversion dashboard. 11 | @JsonKey(name: 'payload') 12 | final Map payload; 13 | 14 | /// Object with the experiment's information. 15 | @JsonKey(name: 'experiment') 16 | final QExperiment? experiment; 17 | 18 | @JsonKey(name: 'source') 19 | final QRemoteConfigurationSource source; 20 | 21 | const QRemoteConfig( 22 | this.payload, 23 | this.experiment, 24 | this.source 25 | ); 26 | 27 | factory QRemoteConfig.fromJson(Map json) => 28 | _$QRemoteConfigFromJson(json); 29 | } 30 | -------------------------------------------------------------------------------- /lib/src/dto/user_property.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | import 'package:qonversion_flutter/src/dto/user_property_key.dart'; 3 | import 'package:qonversion_flutter/src/internal/mapper.dart'; 4 | 5 | part 'user_property.g.dart'; 6 | 7 | @JsonSerializable(createToJson: false) 8 | class QUserProperty { 9 | @JsonKey(name: "key") 10 | final String key; 11 | 12 | @JsonKey(name: "value") 13 | final String value; 14 | 15 | final QUserPropertyKey definedKey; 16 | 17 | QUserProperty._(this.key, this.value, this.definedKey); 18 | 19 | factory QUserProperty(String key, String value) { 20 | final calculatedKey = QMapper.userPropertyKeyFromString(key); 21 | return QUserProperty._(key, value, calculatedKey); 22 | } 23 | 24 | factory QUserProperty.fromJson(Map json) => 25 | _$QUserPropertyFromJson(json); 26 | } 27 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | /build/ 32 | pubspec.lock 33 | 34 | # iOS related 35 | ios/Podfile.lock 36 | 37 | # Android related 38 | android/keystore/ 39 | .cxx/ 40 | 41 | # Web related 42 | lib/generated_plugin_registrant.dart 43 | 44 | # Exceptions to above rules. 45 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 46 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | def flutterSdkPath = { 3 | def properties = new Properties() 4 | file("local.properties").withInputStream { properties.load(it) } 5 | def flutterSdkPath = properties.getProperty("flutter.sdk") 6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 7 | return flutterSdkPath 8 | }() 9 | 10 | includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") 11 | 12 | repositories { 13 | google() 14 | mavenCentral() 15 | gradlePluginPortal() 16 | } 17 | } 18 | 19 | plugins { 20 | id "dev.flutter.flutter-plugin-loader" version "1.0.0" 21 | id "com.android.application" version "8.7.3" apply false 22 | id "org.jetbrains.kotlin.android" version "1.9.25" apply false 23 | id "com.google.gms.google-services" version "4.3.14" apply false 24 | } 25 | 26 | include ":app" -------------------------------------------------------------------------------- /lib/src/dto/sk_product/subscription_period_unit.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | /// Dart wrapper around StoreKit's [SKProductPeriodUnit](https://developer.apple.com/documentation/storekit/skproductperiodunit?language=objc). 4 | /// 5 | /// Used as a property in the [SKProductSubscriptionPeriodWrapper]. Minimum is a day and maximum is a year. 6 | // The values of the enum options are matching the [SKProductPeriodUnit]'s values. Should there be an update or addition 7 | // in the [SKProductPeriodUnit], this need to be updated to match. 8 | enum SKSubscriptionPeriodUnit { 9 | /// An interval lasting one day. 10 | @JsonValue(0) 11 | day, 12 | 13 | /// An interval lasting one week. 14 | @JsonValue(1) 15 | week, 16 | 17 | /// An interval lasting one month. 18 | @JsonValue(2) 19 | month, 20 | 21 | /// An interval lasting one year. 22 | @JsonValue(3) 23 | year, 24 | } 25 | -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 12.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /lib/src/dto/experiment_group.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | import 'experiment_group_type.dart'; 4 | 5 | part 'experiment_group.g.dart'; 6 | 7 | @JsonSerializable(createToJson: false) 8 | class QExperimentGroup { 9 | /// Experiment group's identifier. 10 | @JsonKey(name: 'id') 11 | final String id; 12 | 13 | /// Experiment group's name. The same as you set in Qonversion. You can use it for analytical purposes. 14 | @JsonKey(name: 'name') 15 | final String name; 16 | 17 | /// Type of the experiment's group. Either control or treatment. 18 | @JsonKey( 19 | name: 'type', 20 | unknownEnumValue: QExperimentGroupType.unknown, 21 | ) 22 | final QExperimentGroupType type; 23 | 24 | const QExperimentGroup( 25 | this.id, 26 | this.name, 27 | this.type, 28 | ); 29 | 30 | factory QExperimentGroup.fromJson(Map json) => 31 | _$QExperimentGroupFromJson(json); 32 | } 33 | -------------------------------------------------------------------------------- /lib/src/dto/purchase_model.dart: -------------------------------------------------------------------------------- 1 | import '../qonversion.dart'; 2 | import 'product.dart'; 3 | import './store_product/product_store_details.dart'; 4 | 5 | /// Used to provide all the necessary purchase data to the [Qonversion.purchase] method. 6 | /// Can be created manually or using the [QProduct.toPurchaseModel] method. 7 | /// 8 | /// If [offerId] is not specified for Android, then the default offer will be applied. 9 | /// To know how we choose the default offer, see [QProductStoreDetails.defaultSubscriptionOfferDetails]. 10 | /// 11 | /// If you want to remove any intro/trial offer from the purchase on Android (use only a bare base plan), 12 | /// call the [removeOffer] method. 13 | class QPurchaseModel { 14 | 15 | final String productId; 16 | String? offerId; 17 | bool applyOffer = true; 18 | 19 | QPurchaseModel(this.productId, {this.offerId}); 20 | 21 | QPurchaseModel removeOffer() { 22 | this.applyOffer = false; 23 | return this; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/src/dto/sk_product/discount_payment_mode.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | /// Dart wrapper around StoreKit's [SKProductDiscountPaymentMode](https://developer.apple.com/documentation/storekit/skproductdiscountpaymentmode?language=objc). 4 | /// 5 | /// This is used as a property in the [SKProductDiscountWrapper]. 6 | // The values of the enum options are matching the [SKProductDiscountPaymentMode]'s values. Should there be an update or addition 7 | // in the [SKProductDiscountPaymentMode], this need to be updated to match. 8 | enum SKProductDiscountPaymentMode { 9 | /// Allows user to pay the discounted price at each payment period. 10 | @JsonValue(0) 11 | payAsYouGo, 12 | 13 | /// Allows user to pay the discounted price upfront and receive the product for the rest of time that was paid for. 14 | @JsonValue(1) 15 | payUpFront, 16 | 17 | /// User pays nothing during the discounted period. 18 | @JsonValue(2) 19 | freeTrial, 20 | } 21 | -------------------------------------------------------------------------------- /macos/qonversion_flutter.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. 3 | # Run 'pod lib lint qonversion.podspec' to validate before publishing. 4 | # 5 | Pod::Spec.new do |s| 6 | s.name = 'qonversion_flutter' 7 | s.version = '9.3.0' 8 | s.summary = 'Flutter Qonversion SDK' 9 | s.description = <<-DESC 10 | Powerful yet simple subscription analytics 11 | DESC 12 | s.homepage = 'https://qonversion.io' 13 | s.license = { :file => '../LICENSE' } 14 | s.author = { 'Qonversion Inc.' => 'hi@qonversion.io' } 15 | s.source = { :path => '.' } 16 | s.source_files = 'Classes/**/*' 17 | s.dependency 'FlutterMacOS' 18 | s.platform = :osx, '10.12' 19 | s.dependency "QonversionSandwich", "6.0.10" 20 | 21 | s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } 22 | s.swift_version = '5.0' 23 | s.static_framework = true 24 | end 25 | -------------------------------------------------------------------------------- /lib/src/dto/qonversion_error.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | import 'qonversion_error_code.dart'; 3 | 4 | part 'qonversion_error.g.dart'; 5 | 6 | @JsonSerializable() 7 | class QError { 8 | /// Qonversion Error Code 9 | /// See more in [documentation](https://documentation.qonversion.io/docs/handling-errors) 10 | @JsonKey(name: 'code', unknownEnumValue: QErrorCode.unknown) 11 | final QErrorCode code; 12 | 13 | /// Error description 14 | @JsonKey(name: 'description') 15 | final String message; 16 | 17 | /// Error details 18 | @JsonKey(name: 'additionalMessage') 19 | final String? details; 20 | 21 | const QError(this.code, this.message, this.details); 22 | 23 | factory QError.fromJson(Map json) => _$QErrorFromJson(json); 24 | 25 | Map toJson() => _$QErrorToJson(this); 26 | 27 | @override 28 | String toString() { 29 | return 'Qonversion Error.\nCode: $code, Message: $message, Details: $details'; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/src/dto/store_product/product_installment_plan_details.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'product_installment_plan_details.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | QProductInstallmentPlanDetails _$QProductInstallmentPlanDetailsFromJson( 10 | Map json) => 11 | QProductInstallmentPlanDetails( 12 | (json['commitmentPaymentsCount'] as num).toInt(), 13 | (json['subsequentCommitmentPaymentsCount'] as num).toInt(), 14 | ); 15 | 16 | Map _$QProductInstallmentPlanDetailsToJson( 17 | QProductInstallmentPlanDetails instance) => 18 | { 19 | 'commitmentPaymentsCount': instance.commitmentPaymentsCount, 20 | 'subsequentCommitmentPaymentsCount': 21 | instance.subsequentCommitmentPaymentsCount, 22 | }; 23 | -------------------------------------------------------------------------------- /lib/src/dto/remote_config_list.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | import 'remote_config.dart'; 3 | 4 | part 'remote_config_list.g.dart'; 5 | 6 | @JsonSerializable(createToJson: false) 7 | class QRemoteConfigList { 8 | @JsonKey(name: 'remoteConfigs') 9 | final List remoteConfigs; 10 | 11 | const QRemoteConfigList( 12 | this.remoteConfigs, 13 | ); 14 | 15 | QRemoteConfig? remoteConfigForContextKey(String contextKey) { 16 | return _findRemoteConfig(contextKey); 17 | } 18 | 19 | QRemoteConfig? remoteConfigForEmptyContextKey() { 20 | return _findRemoteConfig(null); 21 | } 22 | 23 | QRemoteConfig? _findRemoteConfig(String? contextKey) { 24 | for (QRemoteConfig config in remoteConfigs) { 25 | if (config.source.contextKey == contextKey) { 26 | return config; 27 | } 28 | } 29 | return null; 30 | } 31 | 32 | factory QRemoteConfigList.fromJson(Map json) => 33 | _$QRemoteConfigListFromJson(json); 34 | } 35 | -------------------------------------------------------------------------------- /android/src/main/kotlin/com/qonversion/flutter/sdk/qonversion_flutter_sdk/BaseListenerWrapper.kt: -------------------------------------------------------------------------------- 1 | package com.qonversion.flutter.sdk.qonversion_flutter_sdk 2 | 3 | import io.flutter.plugin.common.BinaryMessenger 4 | import io.flutter.plugin.common.EventChannel 5 | 6 | class BaseListenerWrapper internal constructor( 7 | private val binaryMessenger: BinaryMessenger, 8 | private val eventChannelPostfix: String 9 | ) { 10 | 11 | private var eventChannel: EventChannel? = null 12 | var eventStreamHandler: BaseEventStreamHandler? = null 13 | 14 | fun register() { 15 | eventStreamHandler = 16 | BaseEventStreamHandler() 17 | eventChannel = EventChannel( 18 | binaryMessenger, 19 | "qonversion_flutter_${eventChannelPostfix}" 20 | ).apply { setStreamHandler(eventStreamHandler) } 21 | } 22 | 23 | fun unregister() { 24 | eventChannel?.setStreamHandler(null) 25 | eventStreamHandler = null 26 | eventChannel = null 27 | } 28 | } -------------------------------------------------------------------------------- /lib/src/dto/store_product/product_price.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'product_price.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | QProductPrice _$QProductPriceFromJson(Map json) => 10 | QProductPrice( 11 | (json['priceAmountMicros'] as num).toInt(), 12 | json['priceCurrencyCode'] as String, 13 | json['formattedPrice'] as String, 14 | json['isFree'] as bool, 15 | json['currencySymbol'] as String?, 16 | ); 17 | 18 | Map _$QProductPriceToJson(QProductPrice instance) => 19 | { 20 | 'priceAmountMicros': instance.priceAmountMicros, 21 | 'priceCurrencyCode': instance.priceCurrencyCode, 22 | 'formattedPrice': instance.formattedPrice, 23 | 'isFree': instance.isFree, 24 | 'currencySymbol': instance.currencySymbol, 25 | }; 26 | -------------------------------------------------------------------------------- /lib/src/dto/sk_product/sk_payment_discount.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'sk_payment_discount.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | SKPaymentDiscount _$SKPaymentDiscountFromJson(Map json) => 10 | SKPaymentDiscount( 11 | identifier: json['identifier'] as String, 12 | keyIdentifier: json['keyIdentifier'] as String, 13 | nonce: json['nonce'] as String, 14 | signature: json['signature'] as String, 15 | timestamp: json['timestamp'] as num, 16 | ); 17 | 18 | Map _$SKPaymentDiscountToJson(SKPaymentDiscount instance) => 19 | { 20 | 'identifier': instance.identifier, 21 | 'keyIdentifier': instance.keyIdentifier, 22 | 'nonce': instance.nonce, 23 | 'signature': instance.signature, 24 | 'timestamp': instance.timestamp, 25 | }; 26 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/src/main/kotlin/com/qonversion/flutter/sdk/qonversion_flutter_sdk/extenstions.kt: -------------------------------------------------------------------------------- 1 | package com.qonversion.flutter.sdk.qonversion_flutter_sdk 2 | 3 | import com.google.gson.Gson 4 | import io.flutter.plugin.common.MethodChannel 5 | import io.qonversion.sandwich.ResultListener 6 | import io.qonversion.sandwich.SandwichError 7 | 8 | internal fun MethodChannel.Result.toResultListener(): ResultListener { 9 | return object : ResultListener { 10 | override fun onError(error: SandwichError) { 11 | sandwichError(error) 12 | } 13 | 14 | override fun onSuccess(data: Map) { 15 | success(data) 16 | } 17 | } 18 | } 19 | 20 | internal fun MethodChannel.Result.toJsonResultListener(): ResultListener { 21 | return object : ResultListener { 22 | override fun onError(error: SandwichError) { 23 | sandwichError(error) 24 | } 25 | 26 | override fun onSuccess(data: Map) { 27 | success(Gson().toJson(data)) 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /ios/qonversion_flutter.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. 3 | # Run 'pod lib lint qonversion.podspec' to validate before publishing. 4 | # 5 | Pod::Spec.new do |s| 6 | s.name = 'qonversion_flutter' 7 | s.version = '9.3.0' 8 | s.summary = 'Flutter Qonversion SDK' 9 | s.description = <<-DESC 10 | Powerful yet simple subscription analytics 11 | DESC 12 | s.homepage = 'https://qonversion.io' 13 | s.license = { :file => '../LICENSE' } 14 | s.author = { 'Qonversion Inc.' => 'hi@qonversion.io' } 15 | s.source = { :path => '.' } 16 | s.source_files = 'Classes/**/*' 17 | s.dependency 'Flutter' 18 | s.platform = :ios, '13.0' 19 | s.dependency "QonversionSandwich", "7.1.0" 20 | 21 | # Flutter.framework does not contain a i386 slice. Only x86_64 simulators are supported. 22 | s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS[sdk=iphonesimulator*]' => 'x86_64' } 23 | s.swift_version = '5.0' 24 | end 25 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: qonversion_flutter 2 | description: Flutter plugin to implement in-app subscriptions and purchases. Validate user receipts and manage cross-platform access to paid content on your app. Android & iOS. 3 | version: 11.0.0 4 | homepage: 'https://qonversion.io' 5 | repository: 'https://github.com/qonversion/flutter-sdk' 6 | 7 | environment: 8 | sdk: '>=2.15.0 <4.0.0' 9 | flutter: ">=3.0.0" 10 | 11 | dependencies: 12 | flutter: 13 | sdk: flutter 14 | json_annotation: ^4.9.0 15 | collection: ^1.15.0 16 | 17 | dev_dependencies: 18 | flutter_test: 19 | sdk: flutter 20 | build_runner: ^2.3.3 21 | json_serializable: ^6.6.2 22 | build_runner_core: ^7.2.7+1 23 | 24 | flutter: 25 | plugin: 26 | platforms: 27 | android: 28 | pluginClass: QonversionPlugin 29 | package: com.qonversion.flutter.sdk.qonversion_flutter_sdk 30 | ios: 31 | pluginClass: QonversionPlugin 32 | macos: 33 | pluginClass: QonversionPlugin 34 | 35 | false_secrets: 36 | - /example/android/app/google-services.json 37 | - /example/ios/GoogleService-Info.plist 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Qonversion team 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /lib/src/dto/offerings.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'offerings.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | QOfferings _$QOfferingsFromJson(Map json) => QOfferings( 10 | json['main'] == null 11 | ? null 12 | : QOffering.fromJson(json['main'] as Map), 13 | (json['availableOfferings'] as List) 14 | .map((e) => QOffering.fromJson(e as Map)) 15 | .toList(), 16 | ); 17 | 18 | QOffering _$QOfferingFromJson(Map json) => QOffering( 19 | json['id'] as String, 20 | $enumDecode(_$QOfferingTagEnumMap, json['tag']), 21 | (json['products'] as List) 22 | .map((e) => QProduct.fromJson(e as Map)) 23 | .toList(), 24 | ); 25 | 26 | const _$QOfferingTagEnumMap = { 27 | QOfferingTag.unknown: -1, 28 | QOfferingTag.none: 0, 29 | QOfferingTag.main: 1, 30 | }; 31 | -------------------------------------------------------------------------------- /lib/src/dto/sk_product/subscription_period.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'subscription_period.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | SKProductSubscriptionPeriod _$SKProductSubscriptionPeriodFromJson( 10 | Map json) => 11 | SKProductSubscriptionPeriod( 12 | numberOfUnits: (json['numberOfUnits'] as num).toInt(), 13 | unit: $enumDecode(_$SKSubscriptionPeriodUnitEnumMap, json['unit']), 14 | ); 15 | 16 | Map _$SKProductSubscriptionPeriodToJson( 17 | SKProductSubscriptionPeriod instance) => 18 | { 19 | 'numberOfUnits': instance.numberOfUnits, 20 | 'unit': _$SKSubscriptionPeriodUnitEnumMap[instance.unit]!, 21 | }; 22 | 23 | const _$SKSubscriptionPeriodUnitEnumMap = { 24 | SKSubscriptionPeriodUnit.day: 0, 25 | SKSubscriptionPeriodUnit.week: 1, 26 | SKSubscriptionPeriodUnit.month: 2, 27 | SKSubscriptionPeriodUnit.year: 3, 28 | }; 29 | -------------------------------------------------------------------------------- /qonversion_flutter_sdk.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /lib/src/dto/purchase_update_policy.dart: -------------------------------------------------------------------------------- 1 | import 'purchase_update_model.dart'; 2 | 3 | /// A policy used for purchase updates on Android, which describes 4 | /// how to migrate from purchased plan to a new one. 5 | /// 6 | /// Used in [QPurchaseUpdateModel] class for purchase updates. 7 | enum QPurchaseUpdatePolicy { 8 | 9 | /// The new plan takes effect immediately, and the user is charged full price of new plan 10 | /// and is given a full billing cycle of subscription, plus remaining prorated time 11 | /// from the old plan. 12 | chargeFullPrice, 13 | 14 | /// The new plan takes effect immediately, and the billing cycle remains the same. 15 | chargeProratedPrice, 16 | 17 | /// The new plan takes effect immediately, and the remaining time will be prorated 18 | /// and credited to the user. 19 | withTimeProration, 20 | 21 | /// The new purchase takes effect immediately, the new plan will take effect 22 | /// when the old item expires. 23 | deferred, 24 | 25 | /// The new plan takes effect immediately, and the new price will be charged 26 | /// on next recurrence time. 27 | withoutProration, 28 | 29 | /// Unknown police. 30 | unknown, 31 | } -------------------------------------------------------------------------------- /lib/src/dto/subscription_period.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part 'subscription_period.g.dart'; 4 | 5 | enum QSubscriptionPeriodUnit { 6 | @JsonValue("Day") 7 | day, 8 | @JsonValue("Week") 9 | week, 10 | @JsonValue("Month") 11 | month, 12 | @JsonValue("Year") 13 | year, 14 | @JsonValue("Unknown") 15 | unknown, 16 | } 17 | 18 | /// A class describing a subscription period 19 | @JsonSerializable() 20 | class QSubscriptionPeriod { 21 | /// A count of subsequent intervals. 22 | @JsonKey(name: 'unitCount') 23 | final int unitCount; 24 | 25 | /// Interval unit. 26 | @JsonKey(name: 'unit', unknownEnumValue: QSubscriptionPeriodUnit.unknown) 27 | final QSubscriptionPeriodUnit unit; 28 | 29 | /// ISO 8601 representation of the period, e.g. "P7D", meaning 7 days period. 30 | @JsonKey(name: 'iso') 31 | final String iso; 32 | 33 | const QSubscriptionPeriod( 34 | this.unitCount, 35 | this.unit, 36 | this.iso, 37 | ); 38 | 39 | factory QSubscriptionPeriod.fromJson(Map json) => 40 | _$QSubscriptionPeriodFromJson(json); 41 | 42 | Map toJson() => _$QSubscriptionPeriodToJson(this); 43 | } -------------------------------------------------------------------------------- /fastlane/README.md: -------------------------------------------------------------------------------- 1 | fastlane documentation 2 | ---- 3 | 4 | # Installation 5 | 6 | Make sure you have the latest version of the Xcode command line tools installed: 7 | 8 | ```sh 9 | xcode-select --install 10 | ``` 11 | 12 | For _fastlane_ installation instructions, see [Installing _fastlane_](https://docs.fastlane.tools/#installing-fastlane) 13 | 14 | # Available Actions 15 | 16 | ### patch 17 | 18 | ```sh 19 | [bundle exec] fastlane patch 20 | ``` 21 | 22 | 23 | 24 | ### minor 25 | 26 | ```sh 27 | [bundle exec] fastlane minor 28 | ``` 29 | 30 | 31 | 32 | ### bump 33 | 34 | ```sh 35 | [bundle exec] fastlane bump 36 | ``` 37 | 38 | 39 | 40 | ### upgrade_sandwich 41 | 42 | ```sh 43 | [bundle exec] fastlane upgrade_sandwich 44 | ``` 45 | 46 | 47 | 48 | ### provide_next_patch_version 49 | 50 | ```sh 51 | [bundle exec] fastlane provide_next_patch_version 52 | ``` 53 | 54 | 55 | 56 | ---- 57 | 58 | This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run. 59 | 60 | More information about _fastlane_ can be found on [fastlane.tools](https://fastlane.tools). 61 | 62 | The documentation of _fastlane_ can be found on [docs.fastlane.tools](https://docs.fastlane.tools). 63 | -------------------------------------------------------------------------------- /lib/src/dto/subscription_period.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'subscription_period.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | QSubscriptionPeriod _$QSubscriptionPeriodFromJson(Map json) => 10 | QSubscriptionPeriod( 11 | (json['unitCount'] as num).toInt(), 12 | $enumDecode(_$QSubscriptionPeriodUnitEnumMap, json['unit'], 13 | unknownValue: QSubscriptionPeriodUnit.unknown), 14 | json['iso'] as String, 15 | ); 16 | 17 | Map _$QSubscriptionPeriodToJson( 18 | QSubscriptionPeriod instance) => 19 | { 20 | 'unitCount': instance.unitCount, 21 | 'unit': _$QSubscriptionPeriodUnitEnumMap[instance.unit]!, 22 | 'iso': instance.iso, 23 | }; 24 | 25 | const _$QSubscriptionPeriodUnitEnumMap = { 26 | QSubscriptionPeriodUnit.day: 'Day', 27 | QSubscriptionPeriodUnit.week: 'Week', 28 | QSubscriptionPeriodUnit.month: 'Month', 29 | QSubscriptionPeriodUnit.year: 'Year', 30 | QSubscriptionPeriodUnit.unknown: 'Unknown', 31 | }; 32 | -------------------------------------------------------------------------------- /example/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | qonversion_flutter_sdk_example 15 | 16 | 17 | 18 | 21 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /lib/src/dto/remote_configuration_source.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | import 'package:qonversion_flutter/src/dto/remote_configuration_assignment_type.dart'; 3 | import 'remote_configuration_source_type.dart'; 4 | 5 | part 'remote_configuration_source.g.dart'; 6 | 7 | @JsonSerializable(createToJson: false) 8 | class QRemoteConfigurationSource { 9 | /// Source's identifier. 10 | @JsonKey(name: 'id') 11 | final String id; 12 | 13 | /// Source's name. 14 | @JsonKey(name: 'name') 15 | final String name; 16 | 17 | @JsonKey( 18 | name: 'type', 19 | unknownEnumValue: QRemoteConfigurationSourceType.unknown, 20 | ) 21 | final QRemoteConfigurationSourceType type; 22 | 23 | @JsonKey( 24 | name: 'assignmentType', 25 | unknownEnumValue: QRemoteConfigurationAssignmentType.unknown, 26 | ) 27 | final QRemoteConfigurationAssignmentType assignmentType; 28 | 29 | @JsonKey(name: 'contextKey') 30 | final String? contextKey; 31 | 32 | const QRemoteConfigurationSource( 33 | this.id, 34 | this.name, 35 | this.type, 36 | this.assignmentType, 37 | this.contextKey, 38 | ); 39 | 40 | factory QRemoteConfigurationSource.fromJson(Map json) => 41 | _$QRemoteConfigurationSourceFromJson(json); 42 | } 43 | -------------------------------------------------------------------------------- /lib/src/dto/store_product/product_installment_plan_details.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part 'product_installment_plan_details.g.dart'; 4 | 5 | /// This class represents the details about the installment plan for a subscription product. 6 | @JsonSerializable() 7 | class QProductInstallmentPlanDetails { 8 | /// Committed payments count after a user signs up for this subscription plan. 9 | @JsonKey(name: 'commitmentPaymentsCount') 10 | final int commitmentPaymentsCount; 11 | 12 | /// Subsequent committed payments count after this subscription plan renews. 13 | /// 14 | /// Returns 0 if the installment plan doesn't have any subsequent commitment, 15 | /// which means this subscription plan will fall back to a normal 16 | /// non-installment monthly plan when the plan renews. 17 | @JsonKey(name: 'subsequentCommitmentPaymentsCount') 18 | final int subsequentCommitmentPaymentsCount; 19 | 20 | const QProductInstallmentPlanDetails( 21 | this.commitmentPaymentsCount, 22 | this.subsequentCommitmentPaymentsCount 23 | ); 24 | 25 | factory QProductInstallmentPlanDetails.fromJson(Map json) => 26 | _$QProductInstallmentPlanDetailsFromJson(json); 27 | 28 | Map toJson() => _$QProductInstallmentPlanDetailsToJson(this); 29 | } -------------------------------------------------------------------------------- /lib/src/dto/purchase_update_model.dart: -------------------------------------------------------------------------------- 1 | import '../qonversion.dart'; 2 | import 'product.dart'; 3 | import './store_product/product_store_details.dart'; 4 | import 'purchase_update_policy.dart'; 5 | 6 | /// Used to provide all the necessary purchase data to the [Qonversion.updatePurchase] method. 7 | /// Can be created manually or using the [QProduct.toPurchaseUpdateModel] method. 8 | /// 9 | /// Requires Qonversion product identifiers - [productId] for the purchasing one and 10 | /// [oldProductId] for the purchased one. 11 | /// 12 | /// If [offerId] is not specified for Android, then the default offer will be applied. 13 | /// To know how we choose the default offer, see [QProductStoreDetails.defaultSubscriptionOfferDetails]. 14 | /// 15 | /// If you want to remove any intro/trial offer from the purchase on Android (use only a bare base plan), 16 | /// call the [removeOffer] method. 17 | class QPurchaseUpdateModel { 18 | 19 | final String productId; 20 | final String oldProductId; 21 | 22 | QPurchaseUpdatePolicy? updatePolicy; 23 | String? offerId; 24 | bool applyOffer = true; 25 | 26 | QPurchaseUpdateModel( 27 | this.productId, 28 | this.oldProductId, 29 | {this.updatePolicy, this.offerId} 30 | ); 31 | 32 | QPurchaseUpdateModel removeOffer() { 33 | this.applyOffer = false; 34 | return this; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/src/dto/store_product/product_price.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part 'product_price.g.dart'; 4 | 5 | /// Information about the Google product's price. 6 | @JsonSerializable() 7 | class QProductPrice { 8 | /// Total amount of money in micro-units, 9 | /// where 1,000,000 micro-units equal one unit of the currency. 10 | @JsonKey(name: 'priceAmountMicros') 11 | final int priceAmountMicros; 12 | 13 | /// ISO 4217 currency code for price. 14 | @JsonKey(name: 'priceCurrencyCode') 15 | final String priceCurrencyCode; 16 | 17 | /// Formatted price for the payment, including its currency sign. 18 | @JsonKey(name: 'formattedPrice') 19 | final String formattedPrice; 20 | 21 | /// True, if the price is zero. False otherwise. 22 | @JsonKey(name: 'isFree') 23 | final bool isFree; 24 | 25 | /// Price currency symbol. Null if failed to parse. 26 | @JsonKey(name: 'currencySymbol') 27 | final String? currencySymbol; 28 | 29 | const QProductPrice( 30 | this.priceAmountMicros, 31 | this.priceCurrencyCode, 32 | this.formattedPrice, 33 | this.isFree, 34 | this.currencySymbol, 35 | ); 36 | 37 | factory QProductPrice.fromJson(Map json) => 38 | _$QProductPriceFromJson(json); 39 | 40 | Map toJson() => _$QProductPriceToJson(this); 41 | } -------------------------------------------------------------------------------- /example/ios/GoogleService-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CLIENT_ID 6 | 11599271839-qalspkpqrihnkl1e12be731tgmre5uop.apps.googleusercontent.com 7 | REVERSED_CLIENT_ID 8 | com.googleusercontent.apps.11599271839-qalspkpqrihnkl1e12be731tgmre5uop 9 | API_KEY 10 | AIzaSyAzsrBRDMPtgKzfpE71jtpumypZjfl-E4I 11 | GCM_SENDER_ID 12 | 11599271839 13 | PLIST_VERSION 14 | 1 15 | BUNDLE_ID 16 | com.qonversion.sample 17 | PROJECT_ID 18 | qonversion-sample-app 19 | STORAGE_BUCKET 20 | qonversion-sample-app.appspot.com 21 | IS_ADS_ENABLED 22 | 23 | IS_ANALYTICS_ENABLED 24 | 25 | IS_APPINVITE_ENABLED 26 | 27 | IS_GCM_ENABLED 28 | 29 | IS_SIGNIN_ENABLED 30 | 31 | GOOGLE_APP_ID 32 | 1:11599271839:ios:abf1959709d9b8dd70cfaa 33 | DATABASE_URL 34 | https://qonversion-sample-app.firebaseio.com 35 | 36 | -------------------------------------------------------------------------------- /example/ios/Flutter/ephemeral/flutter_lldb_helper.py: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | import lldb 6 | 7 | def handle_new_rx_page(frame: lldb.SBFrame, bp_loc, extra_args, intern_dict): 8 | """Intercept NOTIFY_DEBUGGER_ABOUT_RX_PAGES and touch the pages.""" 9 | base = frame.register["x0"].GetValueAsAddress() 10 | page_len = frame.register["x1"].GetValueAsUnsigned() 11 | 12 | # Note: NOTIFY_DEBUGGER_ABOUT_RX_PAGES will check contents of the 13 | # first page to see if handled it correctly. This makes diagnosing 14 | # misconfiguration (e.g. missing breakpoint) easier. 15 | data = bytearray(page_len) 16 | data[0:8] = b'IHELPED!' 17 | 18 | error = lldb.SBError() 19 | frame.GetThread().GetProcess().WriteMemory(base, data, error) 20 | if not error.Success(): 21 | print(f'Failed to write into {base}[+{page_len}]', error) 22 | return 23 | 24 | def __lldb_init_module(debugger: lldb.SBDebugger, _): 25 | target = debugger.GetDummyTarget() 26 | # Caveat: must use BreakpointCreateByRegEx here and not 27 | # BreakpointCreateByName. For some reasons callback function does not 28 | # get carried over from dummy target for the later. 29 | bp = target.BreakpointCreateByRegex("^NOTIFY_DEBUGGER_ABOUT_RX_PAGES$") 30 | bp.SetScriptCallbackFunction('{}.handle_new_rx_page'.format(__name__)) 31 | bp.SetAutoContinue(True) 32 | print("-- LLDB integration loaded --") 33 | -------------------------------------------------------------------------------- /example/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | platform :ios, '13.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | end 36 | 37 | post_install do |installer| 38 | installer.pods_project.targets.each do |target| 39 | flutter_additional_ios_build_settings(target) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/src/dto/offerings.dart: -------------------------------------------------------------------------------- 1 | import 'package:collection/collection.dart' show IterableExtension; 2 | import 'package:json_annotation/json_annotation.dart'; 3 | import 'package:qonversion_flutter/qonversion_flutter.dart'; 4 | 5 | part 'offerings.g.dart'; 6 | 7 | enum QOfferingTag { 8 | @JsonValue(-1) 9 | unknown, 10 | @JsonValue(0) 11 | none, 12 | @JsonValue(1) 13 | main, 14 | } 15 | 16 | @JsonSerializable(createToJson: false) 17 | class QOfferings { 18 | @JsonKey(name: 'main') 19 | final QOffering? main; 20 | 21 | @JsonKey(name: 'availableOfferings') 22 | final List availableOfferings; 23 | 24 | const QOfferings(this.main, this.availableOfferings); 25 | 26 | QOffering? offeringForIdentifier(String id) => 27 | availableOfferings.firstWhereOrNull( 28 | (element) => element.id == id, 29 | ); 30 | 31 | factory QOfferings.fromJson(Map json) => 32 | _$QOfferingsFromJson(json); 33 | } 34 | 35 | @JsonSerializable(createToJson: false) 36 | class QOffering { 37 | @JsonKey(name: 'id') 38 | final String id; 39 | 40 | @JsonKey(name: 'tag') 41 | final QOfferingTag tag; 42 | 43 | @JsonKey(name: 'products') 44 | final List products; 45 | 46 | const QOffering(this.id, this.tag, this.products); 47 | 48 | QProduct? productForIdentifier(String id) => products.firstWhereOrNull( 49 | (element) => element.qonversionId == id, 50 | ); 51 | 52 | factory QOffering.fromJson(Map json) => 53 | _$QOfferingFromJson(json); 54 | } 55 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | group 'com.qonversion.flutter.sdk.qonversion_flutter_sdk' 2 | version '5.0.0' 3 | 4 | buildscript { 5 | ext.kotlin_version = '1.6.10' 6 | repositories { 7 | google() 8 | mavenCentral() 9 | } 10 | 11 | dependencies { 12 | classpath 'com.android.tools.build:gradle:8.2.1' 13 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 14 | } 15 | } 16 | 17 | rootProject.allprojects { 18 | repositories { 19 | google() 20 | mavenCentral() 21 | mavenLocal() 22 | } 23 | } 24 | 25 | apply plugin: 'com.android.library' 26 | apply plugin: 'kotlin-android' 27 | 28 | android { 29 | compileSdk 34 30 | namespace "com.qonversion.flutter.sdk.qonversion_flutter_sdk" 31 | 32 | sourceSets { 33 | main.java.srcDirs += 'src/main/kotlin' 34 | } 35 | defaultConfig { 36 | minSdkVersion 21 37 | targetSdkVersion 34 38 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 39 | } 40 | lintOptions { 41 | disable 'InvalidPackage' 42 | } 43 | compileOptions { 44 | sourceCompatibility JavaVersion.VERSION_1_8 45 | targetCompatibility JavaVersion.VERSION_1_8 46 | } 47 | kotlinOptions { 48 | jvmTarget = '1.8' 49 | } 50 | } 51 | 52 | dependencies { 53 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 54 | implementation "io.qonversion:sandwich:7.1.0" 55 | implementation 'com.google.code.gson:gson:2.9.0' 56 | } 57 | -------------------------------------------------------------------------------- /lib/src/dto/remote_configuration_source.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'remote_configuration_source.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | QRemoteConfigurationSource _$QRemoteConfigurationSourceFromJson( 10 | Map json) => 11 | QRemoteConfigurationSource( 12 | json['id'] as String, 13 | json['name'] as String, 14 | $enumDecode(_$QRemoteConfigurationSourceTypeEnumMap, json['type'], 15 | unknownValue: QRemoteConfigurationSourceType.unknown), 16 | $enumDecode( 17 | _$QRemoteConfigurationAssignmentTypeEnumMap, json['assignmentType'], 18 | unknownValue: QRemoteConfigurationAssignmentType.unknown), 19 | json['contextKey'] as String?, 20 | ); 21 | 22 | const _$QRemoteConfigurationSourceTypeEnumMap = { 23 | QRemoteConfigurationSourceType.unknown: 'unknown', 24 | QRemoteConfigurationSourceType.experimentTreatmentGroup: 25 | 'experiment_treatment_group', 26 | QRemoteConfigurationSourceType.experimentControlGroup: 27 | 'experiment_control_group', 28 | QRemoteConfigurationSourceType.remoteConfiguration: 'remote_configuration', 29 | }; 30 | 31 | const _$QRemoteConfigurationAssignmentTypeEnumMap = { 32 | QRemoteConfigurationAssignmentType.unknown: 'unknown', 33 | QRemoteConfigurationAssignmentType.auto: 'auto', 34 | QRemoteConfigurationAssignmentType.manual: 'manual', 35 | }; 36 | -------------------------------------------------------------------------------- /lib/src/dto/sk_product/sk_product_discount.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'sk_product_discount.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | SKProductDiscount _$SKProductDiscountFromJson(Map json) => 10 | SKProductDiscount( 11 | identifier: json['identifier'] as String?, 12 | price: json['price'] as String, 13 | priceLocale: QMapper.skPriceLocaleFromJson(json['priceLocale']), 14 | numberOfPeriods: (json['numberOfPeriods'] as num).toInt(), 15 | paymentMode: $enumDecode( 16 | _$SKProductDiscountPaymentModeEnumMap, json['paymentMode']), 17 | subscriptionPeriod: QMapper.skProductSubscriptionPeriodFromJson( 18 | json['subscriptionPeriod']), 19 | ); 20 | 21 | Map _$SKProductDiscountToJson(SKProductDiscount instance) => 22 | { 23 | 'identifier': instance.identifier, 24 | 'price': instance.price, 25 | 'priceLocale': instance.priceLocale, 26 | 'numberOfPeriods': instance.numberOfPeriods, 27 | 'paymentMode': 28 | _$SKProductDiscountPaymentModeEnumMap[instance.paymentMode]!, 29 | 'subscriptionPeriod': instance.subscriptionPeriod, 30 | }; 31 | 32 | const _$SKProductDiscountPaymentModeEnumMap = { 33 | SKProductDiscountPaymentMode.payAsYouGo: 0, 34 | SKProductDiscountPaymentMode.payUpFront: 1, 35 | SKProductDiscountPaymentMode.freeTrial: 2, 36 | }; 37 | -------------------------------------------------------------------------------- /lib/src/dto/sk_product/sk_payment_discount.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part 'sk_payment_discount.g.dart'; 4 | 5 | /// Dart wrapper around StoreKit's [SKPaymentDiscount](https://developer.apple.com/documentation/storekit/skpaymentdiscount?language=objc). 6 | /// 7 | /// It is used as a property in [SKProduct]. 8 | @JsonSerializable() 9 | class SKPaymentDiscount { 10 | /// A string used to uniquely identify a discount offer for a product. 11 | final String identifier; 12 | 13 | /// A string that identifies the key used to generate the signature. 14 | final String keyIdentifier; 15 | 16 | /// A universally unique ID (UUID) value that you define. 17 | final String nonce; 18 | 19 | /// A string representing the properties of a specific promotional offer, cryptographically signed. 20 | final String signature; 21 | 22 | /// The date and time of the signature's creation in milliseconds, formatted in Unix epoch time. 23 | final num timestamp; 24 | 25 | /// Creates an [SKPaymentDiscount] with the given discount details. 26 | SKPaymentDiscount({ 27 | required this.identifier, 28 | required this.keyIdentifier, 29 | required this.nonce, 30 | required this.signature, 31 | required this.timestamp, 32 | }); 33 | 34 | /// Constructing an instance from a map from the Objective-C layer. 35 | /// 36 | /// The `map` parameter must not be null. 37 | factory SKPaymentDiscount.fromJson(Map map) { 38 | return _$SKPaymentDiscountFromJson(map); 39 | } 40 | 41 | Map toJson() => _$SKPaymentDiscountToJson(this); 42 | } 43 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | release: 5 | types: [released] 6 | 7 | jobs: 8 | publish: 9 | name: Upload flutter SDK 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v2 14 | 15 | - name: Setup Flutter 16 | uses: subosito/flutter-action@v2 17 | with: 18 | flutter-version: '3.32.4' 19 | channel: 'stable' 20 | 21 | - name: Flutter version 22 | run: flutter --version 23 | 24 | - name: Cache pub dependencies 25 | uses: actions/cache@v3 26 | with: 27 | path: ${{ env.FLUTTER_HOME }}/.pub-cache 28 | key: ${{ runner.os }}-pub-${{ hashFiles('**/pubspec.lock') }} 29 | restore-keys: ${{ runner.os }}-pub- 30 | 31 | - name: Download pub dependencies 32 | run: flutter pub get 33 | 34 | - name: Setup Pub Credentials 35 | run: | 36 | mkdir -p $HOME/.config/dart 37 | cat < $HOME/.config/dart/pub-credentials.json 38 | { 39 | "accessToken": "${{ secrets.PUB_DEV_PUBLISH_ACCESS_TOKEN }}", 40 | "refreshToken": "${{ secrets.PUB_DEV_PUBLISH_REFRESH_TOKEN }}", 41 | "idToken": "${{ secrets.PUB_DEV_PUBLISH_ID_TOKEN }}", 42 | "tokenEndpoint": "${{ secrets.PUB_DEV_PUBLISH_TOKEN_ENDPOINT }}", 43 | "scopes": [ "openid", "https://www.googleapis.com/auth/userinfo.email" ], 44 | "expiration": ${{ secrets.PUB_DEV_PUBLISH_EXPIRATION }} 45 | } 46 | EOF 47 | 48 | - name: Check Publish Warnings 49 | run: flutter pub publish --dry-run 50 | 51 | - name: Publish SDK 52 | run: flutter pub publish -f 53 | -------------------------------------------------------------------------------- /lib/src/dto/product.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'product.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | QProduct _$QProductFromJson(Map json) => QProduct( 10 | json['id'] as String, 11 | json['storeId'] as String?, 12 | json['basePlanId'] as String?, 13 | QMapper.storeProductDetailsFromJson(json['storeDetails']), 14 | QMapper.skProductFromJson(json['skProduct']), 15 | json['offeringId'] as String?, 16 | QMapper.subscriptionPeriodFromJson(json['subscriptionPeriod']), 17 | QMapper.subscriptionPeriodFromJson(json['trialPeriod']), 18 | $enumDecode(_$QProductTypeEnumMap, json['type'], 19 | unknownValue: QProductType.unknown), 20 | json['prettyPrice'] as String?, 21 | ); 22 | 23 | Map _$QProductToJson(QProduct instance) => { 24 | 'id': instance.qonversionId, 25 | 'storeId': instance.storeId, 26 | 'basePlanId': instance.basePlanId, 27 | 'storeDetails': instance.storeDetails, 28 | 'skProduct': instance.skProduct, 29 | 'offeringId': instance.offeringId, 30 | 'subscriptionPeriod': instance.subscriptionPeriod, 31 | 'trialPeriod': instance.trialPeriod, 32 | 'type': _$QProductTypeEnumMap[instance.type]!, 33 | 'prettyPrice': instance.prettyPrice, 34 | }; 35 | 36 | const _$QProductTypeEnumMap = { 37 | QProductType.trial: 'Trial', 38 | QProductType.intro: 'Intro', 39 | QProductType.subscription: 'Subscription', 40 | QProductType.inApp: 'InApp', 41 | QProductType.unknown: 'Unknown', 42 | }; 43 | -------------------------------------------------------------------------------- /ios/Classes/FlutterError+Custom.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FlutterError+Custom.swift 3 | // Pods-Runner 4 | // 5 | // Created by Ilya Virnik on 6/21/20. 6 | // 7 | 8 | #if canImport(FlutterMacOS) 9 | import FlutterMacOS 10 | #else 11 | import Flutter 12 | #endif 13 | 14 | import QonversionSandwich 15 | 16 | extension FlutterError { 17 | static private let passValidValue = "Please make sure you pass a valid value" 18 | 19 | static let noNecessaryData = FlutterError(code: "NoNecessaryDataError", 20 | message: "Could not find necessary arguments", 21 | details: "Make sure you pass correct call arguments") 22 | 23 | static func sandwichError(_ error: SandwichError) -> FlutterError { 24 | return mapSandwichError(error, errorCode: error.code) 25 | } 26 | 27 | static let serializationError = FlutterError(code: "SerializationError", 28 | message: "Failed to serialize response from native bridge", 29 | details: "") 30 | 31 | private static func mapSandwichError(_ error: SandwichError, errorCode: String, errorMessage: String? = nil) -> FlutterError { 32 | var message = "" 33 | 34 | if let errorMessage = errorMessage { 35 | message = errorMessage + ". " 36 | } 37 | message += error.details 38 | 39 | var details = "Qonversion Error Code: \(error.code)" 40 | 41 | if let additionalMessage = error.additionalMessage { 42 | details = "\(details). Additional Message: \(additionalMessage)" 43 | } 44 | 45 | return FlutterError(code: errorCode, 46 | message: message, 47 | details: details) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /macos/Classes/FlutterError+Custom.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FlutterError+Custom.swift 3 | // Pods-Runner 4 | // 5 | // Created by Ilya Virnik on 6/21/20. 6 | // 7 | 8 | #if canImport(FlutterMacOS) 9 | import FlutterMacOS 10 | #else 11 | import Flutter 12 | #endif 13 | 14 | import QonversionSandwich 15 | 16 | extension FlutterError { 17 | static private let passValidValue = "Please make sure you pass a valid value" 18 | 19 | static let noNecessaryData = FlutterError(code: "NoNecessaryDataError", 20 | message: "Could not find necessary arguments", 21 | details: "Make sure you pass correct call arguments") 22 | 23 | static func sandwichError(_ error: SandwichError) -> FlutterError { 24 | return mapSandwichError(error, errorCode: error.code) 25 | } 26 | 27 | static let serializationError = FlutterError(code: "SerializationError", 28 | message: "Failed to serialize response from native bridge", 29 | details: "") 30 | 31 | private static func mapSandwichError(_ error: SandwichError, errorCode: String, errorMessage: String? = nil) -> FlutterError { 32 | var message = "" 33 | 34 | if let errorMessage = errorMessage { 35 | message = errorMessage + ". " 36 | } 37 | message += error.details 38 | 39 | var details = "Qonversion Error Code: \(error.code)" 40 | 41 | if let additionalMessage = error.additionalMessage { 42 | details = "\(details). Additional Message: \(additionalMessage)" 43 | } 44 | 45 | return FlutterError(code: errorCode, 46 | message: message, 47 | details: details) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lib/src/dto/store_product/product_offer_details.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'product_offer_details.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | QProductOfferDetails _$QProductOfferDetailsFromJson( 10 | Map json) => 11 | QProductOfferDetails( 12 | json['basePlanId'] as String, 13 | json['offerId'] as String?, 14 | json['offerToken'] as String, 15 | (json['tags'] as List).map((e) => e as String).toList(), 16 | QMapper.productPricingPhaseListFromJson(json['pricingPhases']), 17 | QMapper.productPricingPhaseFromJson(json['basePlan']), 18 | QMapper.productInstallmentPlanDetailsFromJson( 19 | json['installmentPlanDetails']), 20 | QMapper.productPricingPhaseFromJson(json['introPhase']), 21 | QMapper.productPricingPhaseFromJson(json['trialPhase']), 22 | json['hasTrial'] as bool, 23 | json['hasIntro'] as bool, 24 | json['hasTrialOrIntro'] as bool, 25 | ); 26 | 27 | Map _$QProductOfferDetailsToJson( 28 | QProductOfferDetails instance) => 29 | { 30 | 'basePlanId': instance.basePlanId, 31 | 'offerId': instance.offerId, 32 | 'offerToken': instance.offerToken, 33 | 'tags': instance.tags, 34 | 'pricingPhases': instance.pricingPhases, 35 | 'basePlan': instance.basePlan, 36 | 'installmentPlanDetails': instance.installmentPlanDetails, 37 | 'introPhase': instance.introPhase, 38 | 'trialPhase': instance.trialPhase, 39 | 'hasTrial': instance.hasTrial, 40 | 'hasIntro': instance.hasIntro, 41 | 'hasTrialOrIntro': instance.hasTrialOrIntro, 42 | }; 43 | -------------------------------------------------------------------------------- /macos/Classes/BaseListenerWrapper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseListenerWrapper.swift 3 | // qonversion_flutter 4 | // 5 | // Created by Ilya Virnik on 2/7/21. 6 | // 7 | 8 | #if canImport(FlutterMacOS) 9 | import FlutterMacOS 10 | #else 11 | import Flutter 12 | #endif 13 | 14 | typealias MethodCodec = NSObject & FlutterMethodCodec 15 | 16 | class FlutterListenerWrapper: NSObject where T: EventStreamHandler { 17 | let binding: FlutterPluginRegistrar 18 | let eventChannelPostfix: String 19 | 20 | var eventChannel: FlutterEventChannel? 21 | var eventStreamHandler: T? 22 | 23 | init(_ binding: FlutterPluginRegistrar, postfix: String) { 24 | self.binding = binding 25 | self.eventChannelPostfix = postfix 26 | } 27 | 28 | func register(_ codec: MethodCodec? = nil, completion: ((T?) -> Void)? = nil) { 29 | guard eventStreamHandler == nil else { 30 | return 31 | } 32 | 33 | let messenger: FlutterBinaryMessenger 34 | #if canImport(FlutterMacOS) 35 | messenger = binding.messenger 36 | #else 37 | messenger = binding.messenger() 38 | #endif 39 | 40 | eventStreamHandler = T() 41 | if let codec = codec { 42 | eventChannel = FlutterEventChannel(name: "qonversion_flutter_\(eventChannelPostfix)", 43 | binaryMessenger: messenger, 44 | codec: codec) 45 | } else { 46 | eventChannel = FlutterEventChannel(name: "qonversion_flutter_\(eventChannelPostfix)", 47 | binaryMessenger: messenger) 48 | } 49 | 50 | eventChannel?.setStreamHandler(eventStreamHandler) 51 | 52 | completion?(eventStreamHandler) 53 | } 54 | 55 | func unregister() { 56 | eventChannel?.setStreamHandler(nil) 57 | eventStreamHandler = nil 58 | eventChannel = nil 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /ios/Classes/BaseListenerWrapper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseListenerWrapper.swift 3 | // qonversion_flutter 4 | // 5 | // Created by Ilya Virnik on 2/7/21. 6 | // 7 | 8 | #if canImport(FlutterMacOS) 9 | import FlutterMacOS 10 | #else 11 | import Flutter 12 | #endif 13 | 14 | typealias MethodCodec = NSObject & FlutterMethodCodec 15 | 16 | class FlutterListenerWrapper: NSObject where T: EventStreamHandler { 17 | let binding: FlutterPluginRegistrar 18 | let eventChannelPostfix: String 19 | 20 | var eventChannel: FlutterEventChannel? 21 | var eventStreamHandler: T? 22 | 23 | init(_ binding: FlutterPluginRegistrar, postfix: String) { 24 | self.binding = binding 25 | self.eventChannelPostfix = postfix 26 | } 27 | 28 | func register(_ codec: MethodCodec? = nil, completion: ((T?) -> Void)? = nil) { 29 | guard eventStreamHandler == nil else { 30 | return 31 | } 32 | 33 | let messenger: FlutterBinaryMessenger 34 | #if canImport(FlutterMacOS) 35 | messenger = binding.messenger 36 | #else 37 | messenger = binding.messenger() 38 | #endif 39 | 40 | eventStreamHandler = T() 41 | 42 | let channelName = "qonversion_flutter_\(eventChannelPostfix)" 43 | 44 | if let codec = codec { 45 | eventChannel = FlutterEventChannel(name: channelName, 46 | binaryMessenger: messenger, 47 | codec: codec) 48 | } else { 49 | eventChannel = FlutterEventChannel(name: channelName, 50 | binaryMessenger: messenger) 51 | } 52 | 53 | eventChannel?.setStreamHandler(eventStreamHandler) 54 | 55 | completion?(eventStreamHandler) 56 | } 57 | 58 | func unregister() { 59 | eventChannel?.setStreamHandler(nil) 60 | eventStreamHandler = nil 61 | eventChannel = nil 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | qonversion_flutter_sdk_example 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UIBackgroundModes 26 | 27 | fetch 28 | remote-notification 29 | 30 | UILaunchStoryboardName 31 | LaunchScreen 32 | UIMainStoryboardFile 33 | Main 34 | UISupportedInterfaceOrientations 35 | 36 | UIInterfaceOrientationPortrait 37 | UIInterfaceOrientationLandscapeLeft 38 | UIInterfaceOrientationLandscapeRight 39 | 40 | UISupportedInterfaceOrientations~ipad 41 | 42 | UIInterfaceOrientationPortrait 43 | UIInterfaceOrientationPortraitUpsideDown 44 | UIInterfaceOrientationLandscapeLeft 45 | UIInterfaceOrientationLandscapeRight 46 | 47 | UIViewControllerBasedStatusBarAppearance 48 | 49 | CADisableMinimumFrameDurationOnPhone 50 | 51 | UIApplicationSupportsIndirectInputEvents 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /lib/src/dto/sk_product/subscription_period.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | import 'subscription_period_unit.dart'; 4 | 5 | part 'subscription_period.g.dart'; 6 | 7 | /// Dart wrapper around StoreKit's [SKProductSubscriptionPeriod](https://developer.apple.com/documentation/storekit/skproductsubscriptionperiod?language=objc). 8 | /// 9 | /// 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. 10 | /// It is used as a property in [SKProductDiscountWrapper] and [SKProductWrapper]. 11 | @JsonSerializable() 12 | class SKProductSubscriptionPeriod { 13 | /// Creates an [SKProductSubscriptionPeriod] for a `numberOfUnits`x`unit` period. 14 | 15 | /// The number of [unit] units in this period. 16 | /// 17 | /// Must be greater than 0. 18 | final int numberOfUnits; 19 | 20 | /// The time unit used to specify the length of this period. 21 | final SKSubscriptionPeriodUnit unit; 22 | 23 | SKProductSubscriptionPeriod({ 24 | required this.numberOfUnits, 25 | required this.unit, 26 | }); 27 | 28 | /// Constructing an instance from a map from the Objective-C layer. 29 | /// 30 | /// This method should only be used with `map` values returned by [SKProductDiscount.fromJson] or [SKProduct.fromJson]. 31 | /// The `map` parameter must not be null. 32 | factory SKProductSubscriptionPeriod.fromJson( 33 | Map map) { 34 | return _$SKProductSubscriptionPeriodFromJson(map); 35 | } 36 | 37 | Map toJson() => 38 | _$SKProductSubscriptionPeriodToJson(this); 39 | 40 | @override 41 | bool operator ==(Object other) { 42 | if (identical(other, this)) { 43 | return true; 44 | } 45 | if (other.runtimeType != runtimeType) { 46 | return false; 47 | } 48 | return other is SKProductSubscriptionPeriod && 49 | other.numberOfUnits == numberOfUnits && 50 | other.unit == unit; 51 | } 52 | 53 | @override 54 | int get hashCode => Object.hash(this.numberOfUnits, this.unit); 55 | } 56 | -------------------------------------------------------------------------------- /lib/src/dto/transaction.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'transaction.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | QTransaction _$QTransactionFromJson(Map json) => QTransaction( 10 | json['originalTransactionId'] as String, 11 | json['transactionId'] as String, 12 | json['offerCode'] as String?, 13 | json['promoOfferId'] as String?, 14 | QMapper.dateTimeFromSecondsTimestamp(json['transactionTimestamp'] as num), 15 | QMapper.dateTimeFromNullableSecondsTimestamp( 16 | json['expirationTimestamp'] as num?), 17 | QMapper.dateTimeFromNullableSecondsTimestamp( 18 | json['transactionRevocationTimestamp'] as num?), 19 | $enumDecode(_$QTransactionEnvironmentEnumMap, json['environment'], 20 | unknownValue: QTransactionEnvironment.production), 21 | $enumDecode(_$QTransactionOwnershipTypeEnumMap, json['ownershipType'], 22 | unknownValue: QTransactionOwnershipType.owner), 23 | $enumDecode(_$QTransactionTypeEnumMap, json['type'], 24 | unknownValue: QTransactionType.unknown), 25 | ); 26 | 27 | const _$QTransactionEnvironmentEnumMap = { 28 | QTransactionEnvironment.production: 'Production', 29 | QTransactionEnvironment.sandbox: 'Sandbox', 30 | }; 31 | 32 | const _$QTransactionOwnershipTypeEnumMap = { 33 | QTransactionOwnershipType.owner: 'Owner', 34 | QTransactionOwnershipType.familySharing: 'FamilySharing', 35 | }; 36 | 37 | const _$QTransactionTypeEnumMap = { 38 | QTransactionType.unknown: 'Unknown', 39 | QTransactionType.subscriptionStarted: 'SubscriptionStarted', 40 | QTransactionType.subscriptionRenewed: 'SubscriptionRenewed', 41 | QTransactionType.trialStrated: 'TrialStarted', 42 | QTransactionType.introStarted: 'IntroStarted', 43 | QTransactionType.introRenewed: 'IntroRenewed', 44 | QTransactionType.nonConsumablePurchase: 'NonConsumablePurchase', 45 | }; 46 | -------------------------------------------------------------------------------- /lib/src/dto/entitlement.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'entitlement.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | QEntitlement _$QEntitlementFromJson(Map json) => QEntitlement( 10 | json['id'] as String, 11 | json['productId'] as String, 12 | $enumDecode(_$QEntitlementRenewStateEnumMap, json['renewState'], 13 | unknownValue: QEntitlementRenewState.unknown), 14 | $enumDecode(_$QEntitlementSourceEnumMap, json['source'], 15 | unknownValue: QEntitlementSource.unknown), 16 | QMapper.dateTimeFromNullableSecondsTimestamp( 17 | json['startedTimestamp'] as num?), 18 | QMapper.dateTimeFromNullableSecondsTimestamp( 19 | json['expirationTimestamp'] as num?), 20 | json['active'] as bool, 21 | (json['renewsCount'] as num?)?.toInt() ?? 0, 22 | QMapper.dateTimeFromNullableSecondsTimestamp( 23 | json['trialStartTimestamp'] as num?), 24 | QMapper.dateTimeFromNullableSecondsTimestamp( 25 | json['firstPurchaseTimestamp'] as num?), 26 | QMapper.dateTimeFromNullableSecondsTimestamp( 27 | json['lastPurchaseTimestamp'] as num?), 28 | json['lastActivatedOfferCode'] as String?, 29 | QMapper.grantTypeFromNullableValue(json['grantType'] as String?), 30 | QMapper.dateTimeFromNullableSecondsTimestamp( 31 | json['autoRenewDisableTimestamp'] as num?), 32 | QMapper.transactionsFromNullableValue(json['transactions'] as List?), 33 | ); 34 | 35 | const _$QEntitlementRenewStateEnumMap = { 36 | QEntitlementRenewState.nonRenewable: 'non_renewable', 37 | QEntitlementRenewState.unknown: 'unknown', 38 | QEntitlementRenewState.willRenew: 'will_renew', 39 | QEntitlementRenewState.canceled: 'canceled', 40 | QEntitlementRenewState.billingIssue: 'billing_issue', 41 | }; 42 | 43 | const _$QEntitlementSourceEnumMap = { 44 | QEntitlementSource.unknown: 'Unknown', 45 | QEntitlementSource.appStore: 'AppStore', 46 | QEntitlementSource.playStore: 'PlayStore', 47 | QEntitlementSource.stripe: 'Stripe', 48 | QEntitlementSource.manual: 'Manual', 49 | }; 50 | -------------------------------------------------------------------------------- /lib/src/dto/store_product/product_pricing_phase.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'product_pricing_phase.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | QProductPricingPhase _$QProductPricingPhaseFromJson( 10 | Map json) => 11 | QProductPricingPhase( 12 | QMapper.requiredProductPriceFromJson(json['price']), 13 | QMapper.requiredSubscriptionPeriodFromJson(json['billingPeriod']), 14 | (json['billingCycleCount'] as num).toInt(), 15 | $enumDecode(_$QPricingPhaseRecurrenceModeEnumMap, json['recurrenceMode'], 16 | unknownValue: QPricingPhaseRecurrenceMode.unknown), 17 | $enumDecode(_$QPricingPhaseTypeEnumMap, json['type'], 18 | unknownValue: QPricingPhaseType.unknown), 19 | json['isTrial'] as bool, 20 | json['isIntro'] as bool, 21 | json['isBasePlan'] as bool, 22 | ); 23 | 24 | Map _$QProductPricingPhaseToJson( 25 | QProductPricingPhase instance) => 26 | { 27 | 'price': instance.price, 28 | 'billingPeriod': instance.billingPeriod, 29 | 'billingCycleCount': instance.billingCycleCount, 30 | 'recurrenceMode': 31 | _$QPricingPhaseRecurrenceModeEnumMap[instance.recurrenceMode]!, 32 | 'type': _$QPricingPhaseTypeEnumMap[instance.type]!, 33 | 'isTrial': instance.isTrial, 34 | 'isIntro': instance.isIntro, 35 | 'isBasePlan': instance.isBasePlan, 36 | }; 37 | 38 | const _$QPricingPhaseRecurrenceModeEnumMap = { 39 | QPricingPhaseRecurrenceMode.infiniteRecurring: 'InfiniteRecurring', 40 | QPricingPhaseRecurrenceMode.finiteRecurring: 'FiniteRecurring', 41 | QPricingPhaseRecurrenceMode.nonRecurring: 'NonRecurring', 42 | QPricingPhaseRecurrenceMode.unknown: 'Unknown', 43 | }; 44 | 45 | const _$QPricingPhaseTypeEnumMap = { 46 | QPricingPhaseType.regular: 'Regular', 47 | QPricingPhaseType.freeTrial: 'FreeTrial', 48 | QPricingPhaseType.discountedSinglePayment: 'DiscountedSinglePayment', 49 | QPricingPhaseType.discountedRecurringPayment: 'DiscountedRecurringPayment', 50 | QPricingPhaseType.unknown: 'Unknown', 51 | }; 52 | -------------------------------------------------------------------------------- /lib/src/dto/sk_product/sk_product.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'sk_product.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | SKProduct _$SKProductFromJson(Map json) => SKProduct( 10 | productIdentifier: json['productIdentifier'] as String, 11 | localizedTitle: json['localizedTitle'] as String?, 12 | localizedDescription: json['localizedDescription'] as String?, 13 | priceLocale: QMapper.skPriceLocaleFromJson(json['priceLocale']), 14 | subscriptionGroupIdentifier: 15 | json['subscriptionGroupIdentifier'] as String?, 16 | price: json['price'] as String, 17 | subscriptionPeriod: QMapper.skProductSubscriptionPeriodFromJson( 18 | json['subscriptionPeriod']), 19 | introductoryPrice: 20 | QMapper.skProductDiscountFromJson(json['introductoryPrice']), 21 | productDiscount: 22 | QMapper.skProductDiscountFromJson(json['productDiscount']), 23 | discounts: QMapper.skProductDiscountsFromList(json['discounts'] as List?), 24 | ); 25 | 26 | Map _$SKProductToJson(SKProduct instance) => { 27 | 'productIdentifier': instance.productIdentifier, 28 | 'localizedTitle': instance.localizedTitle, 29 | 'localizedDescription': instance.localizedDescription, 30 | 'priceLocale': instance.priceLocale, 31 | 'subscriptionGroupIdentifier': instance.subscriptionGroupIdentifier, 32 | 'price': instance.price, 33 | 'subscriptionPeriod': instance.subscriptionPeriod, 34 | 'introductoryPrice': instance.introductoryPrice, 35 | 'productDiscount': instance.productDiscount, 36 | 'discounts': instance.discounts, 37 | }; 38 | 39 | SKPriceLocale _$SKPriceLocaleFromJson(Map json) => 40 | SKPriceLocale( 41 | currencySymbol: json['currencySymbol'] as String?, 42 | currencyCode: json['currencyCode'] as String?, 43 | ); 44 | 45 | Map _$SKPriceLocaleToJson(SKPriceLocale instance) => 46 | { 47 | 'currencySymbol': instance.currencySymbol, 48 | 'currencyCode': instance.currencyCode, 49 | }; 50 | -------------------------------------------------------------------------------- /lib/src/dto/presentation_config.dart: -------------------------------------------------------------------------------- 1 | /// Presentation style for NoCodes screens 2 | enum NoCodesPresentationStyle { 3 | push, 4 | fullScreen, 5 | popover, 6 | } 7 | 8 | /// Configuration for NoCodes screen presentation 9 | class NoCodesPresentationConfig { 10 | static const kPush = 'Push'; 11 | static const kFullScreen = 'FullScreen'; 12 | static const kPopover = 'Popover'; 13 | static const kAnimated = 'animated'; 14 | static const kPresentationStyle = 'presentationStyle'; 15 | 16 | final bool animated; 17 | final NoCodesPresentationStyle presentationStyle; 18 | 19 | const NoCodesPresentationConfig({ 20 | this.animated = true, 21 | this.presentationStyle = NoCodesPresentationStyle.fullScreen, 22 | }); 23 | 24 | factory NoCodesPresentationConfig.fromMap(Map map) { 25 | final presentationStyleString = map[kPresentationStyle] as String?; 26 | NoCodesPresentationStyle presentationStyle; 27 | 28 | switch (presentationStyleString) { 29 | case kPush: 30 | presentationStyle = NoCodesPresentationStyle.push; 31 | break; 32 | case kFullScreen: 33 | presentationStyle = NoCodesPresentationStyle.fullScreen; 34 | break; 35 | case kPopover: 36 | presentationStyle = NoCodesPresentationStyle.popover; 37 | break; 38 | default: 39 | presentationStyle = NoCodesPresentationStyle.fullScreen; 40 | } 41 | 42 | return NoCodesPresentationConfig( 43 | animated: map[kAnimated] as bool? ?? true, 44 | presentationStyle: presentationStyle, 45 | ); 46 | } 47 | 48 | Map toMap() { 49 | String presentationStyleString; 50 | switch (presentationStyle) { 51 | case NoCodesPresentationStyle.push: 52 | presentationStyleString = kPush; 53 | break; 54 | case NoCodesPresentationStyle.fullScreen: 55 | presentationStyleString = kFullScreen; 56 | break; 57 | case NoCodesPresentationStyle.popover: 58 | presentationStyleString = kPopover; 59 | break; 60 | } 61 | 62 | return { 63 | kAnimated: animated, 64 | kPresentationStyle: presentationStyleString, 65 | }; 66 | } 67 | 68 | @override 69 | String toString() { 70 | return 'NoCodesPresentationConfig(animated: $animated, presentationStyle: $presentationStyle)'; 71 | } 72 | } -------------------------------------------------------------------------------- /lib/qonversion_flutter.dart: -------------------------------------------------------------------------------- 1 | export 'src/dto/attribution_provider.dart'; 2 | export 'src/dto/eligibility.dart'; 3 | export 'src/dto/entitlement.dart'; 4 | export 'src/dto/entitlement_grant_type.dart'; 5 | export 'src/dto/entitlement_renew_state.dart'; 6 | export 'src/dto/entitlement_source.dart'; 7 | export 'src/dto/entitlements_cache_lifetime.dart'; 8 | export 'src/dto/environment.dart'; 9 | export 'src/dto/launch_mode.dart'; 10 | export 'src/dto/nocodes_events.dart'; 11 | export 'src/dto/offerings.dart'; 12 | export 'src/dto/presentation_config.dart'; 13 | export 'src/dto/product.dart'; 14 | export 'src/dto/product_type.dart'; 15 | export 'src/dto/promotional_offer.dart'; 16 | export 'src/dto/purchase_model.dart'; 17 | export 'src/dto/purchase_options.dart'; 18 | export 'src/dto/purchase_options_builder.dart'; 19 | export 'src/dto/purchase_update_model.dart'; 20 | export 'src/dto/purchase_update_policy.dart'; 21 | export 'src/dto/purchase_exception.dart'; 22 | export 'src/dto/qonversion_exception.dart'; 23 | export 'src/dto/subscription_period.dart'; 24 | export 'src/dto/qonversion_error.dart'; 25 | export 'src/dto/qonversion_error_code.dart'; 26 | export 'src/dto/remote_config.dart'; 27 | export 'src/dto/remote_config_list.dart'; 28 | export 'src/dto/remote_configuration_source.dart'; 29 | export 'src/dto/remote_configuration_source_type.dart'; 30 | export 'src/dto/remote_configuration_assignment_type.dart'; 31 | export 'src/dto/user.dart'; 32 | export 'src/dto/user_properties.dart'; 33 | export 'src/dto/user_property.dart'; 34 | export 'src/dto/user_property_key.dart'; 35 | export 'src/dto/sk_product/discount_payment_mode.dart'; 36 | export 'src/dto/sk_product/sk_payment_discount.dart'; 37 | export 'src/dto/sk_product/sk_product.dart'; 38 | export 'src/dto/sk_product/sk_product_discount.dart'; 39 | export 'src/dto/sk_product/subscription_period.dart'; 40 | export 'src/dto/sk_product/subscription_period_unit.dart'; 41 | export 'src/dto/store_product/product_inapp_details.dart'; 42 | export 'src/dto/store_product/product_installment_plan_details.dart'; 43 | export 'src/dto/store_product/product_offer_details.dart'; 44 | export 'src/dto/store_product/product_price.dart'; 45 | export 'src/dto/store_product/product_pricing_phase.dart'; 46 | export 'src/dto/store_product/product_store_details.dart'; 47 | export 'src/nocodes.dart'; 48 | export 'src/nocodes_config.dart'; 49 | export 'src/nocodes_config_builder.dart'; 50 | export 'src/qonversion.dart'; 51 | export 'src/qonversion_config.dart'; 52 | export 'src/qonversion_config_builder.dart'; 53 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /lib/src/dto/transaction.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | import 'package:qonversion_flutter/src/dto/transaction_environment.dart'; 3 | import 'package:qonversion_flutter/src/dto/transaction_ownership_type.dart'; 4 | import 'package:qonversion_flutter/src/dto/transaction_type.dart'; 5 | 6 | import '../internal/mapper.dart'; 7 | 8 | part 'transaction.g.dart'; 9 | 10 | @JsonSerializable(createToJson: false) 11 | class QTransaction { 12 | /// Original transaction identifier. 13 | @JsonKey(name: 'originalTransactionId') 14 | final String originalTransactionId; 15 | 16 | /// Transaction identifier. 17 | @JsonKey(name: 'transactionId') 18 | final String transactionId; 19 | 20 | /// Offer code. 21 | @JsonKey(name: 'offerCode') 22 | final String? offerCode; 23 | 24 | /// Promotional offer id. 25 | @JsonKey(name: 'promoOfferId') 26 | final String? promoOfferId; 27 | 28 | /// Transaction date. 29 | @JsonKey( 30 | name: 'transactionTimestamp', 31 | fromJson: QMapper.dateTimeFromSecondsTimestamp, 32 | ) 33 | final DateTime transactionDate; 34 | 35 | /// Expiration date for subscriptions. 36 | @JsonKey( 37 | name: 'expirationTimestamp', 38 | fromJson: QMapper.dateTimeFromNullableSecondsTimestamp, 39 | ) 40 | final DateTime? expirationDate; 41 | 42 | /// The date when transaction was revoked. 43 | /// This field represents the time and date the App Store refunded a transaction or revoked it from family sharing. 44 | @JsonKey( 45 | name: 'transactionRevocationTimestamp', 46 | fromJson: QMapper.dateTimeFromNullableSecondsTimestamp, 47 | ) 48 | final DateTime? transactionRevocationDate; 49 | 50 | /// Environment of the transaction. 51 | @JsonKey( 52 | name: 'environment', 53 | unknownEnumValue: QTransactionEnvironment.production, 54 | ) 55 | final QTransactionEnvironment environment; 56 | 57 | /// Type of ownership for the transaction. Owner/Family sharing. 58 | @JsonKey( 59 | name: 'ownershipType', 60 | unknownEnumValue: QTransactionOwnershipType.owner, 61 | ) 62 | final QTransactionOwnershipType ownershipType; 63 | 64 | /// Type of the transaction. 65 | @JsonKey( 66 | name: 'type', 67 | unknownEnumValue: QTransactionType.unknown, 68 | ) 69 | final QTransactionType type; 70 | 71 | const QTransaction( 72 | this.originalTransactionId, 73 | this.transactionId, 74 | this.offerCode, 75 | this.promoOfferId, 76 | this.transactionDate, 77 | this.expirationDate, 78 | this.transactionRevocationDate, 79 | this.environment, 80 | this.ownershipType, 81 | this.type, 82 | ); 83 | 84 | factory QTransaction.fromJson(Map json) => 85 | _$QTransactionFromJson(json); 86 | } 87 | -------------------------------------------------------------------------------- /lib/src/qonversion_config_builder.dart: -------------------------------------------------------------------------------- 1 | import '../qonversion_flutter.dart'; 2 | 3 | class QonversionConfigBuilder { 4 | final String projectKey; 5 | 6 | final QLaunchMode launchMode; 7 | 8 | QEnvironment _environment = QEnvironment.production; 9 | 10 | QEntitlementsCacheLifetime _entitlementsCacheLifetime = QEntitlementsCacheLifetime.month; 11 | 12 | String? _proxyUrl; 13 | 14 | bool _kidsMode = false; 15 | 16 | QonversionConfigBuilder(this.projectKey, this.launchMode); 17 | 18 | 19 | /// Set current application [QEnvironment]. Used to distinguish sandbox and production users. 20 | /// 21 | /// [environment] current environment. 22 | /// Returns builder instance for chain calls. 23 | QonversionConfigBuilder setEnvironment(QEnvironment environment) { 24 | _environment = environment; 25 | return this; 26 | } 27 | 28 | /// Entitlements cache is used when there are problems with the Qonversion API 29 | /// or internet connection. If so, Qonversion will return the last successfully loaded 30 | /// entitlements. The current method allows you to configure how long that cache may be used. 31 | /// The default value is [QEntitlementsCacheLifetime.month]. 32 | /// 33 | /// [lifetime] desired entitlements cache lifetime duration 34 | /// Returns builder instance for chain calls. 35 | QonversionConfigBuilder setEntitlementsCacheLifetime(QEntitlementsCacheLifetime lifetime) { 36 | _entitlementsCacheLifetime = lifetime; 37 | return this; 38 | } 39 | 40 | /// Provide a URL to your proxy server which will redirect all the requests from the app 41 | /// to our API. Please, contact us before using this feature. 42 | /// 43 | /// [url] your proxy server url 44 | /// Returns builder instance for chain calls. 45 | /// See [the documentation](https://documentation.qonversion.io/docs/custom-proxy-server-for-sdks) 46 | QonversionConfigBuilder setProxyURL(String url) { 47 | _proxyUrl = url; 48 | return this; 49 | } 50 | 51 | /// Android only. 52 | /// Use this function to enable Qonversion SDK Kids mode. 53 | /// With this mode activated, our SDK does not collect any information that violates Google Children’s Privacy Policy. 54 | /// 55 | /// Returns builder instance for chain calls. 56 | QonversionConfigBuilder enableKidsMode() 57 | { 58 | _kidsMode = true; 59 | return this; 60 | } 61 | 62 | /// Generate [QonversionConfig] instance with all the provided configurations. 63 | /// 64 | /// Returns the complete [QonversionConfig] instance. 65 | QonversionConfig build() { 66 | return new QonversionConfig( 67 | projectKey, 68 | launchMode, 69 | _environment, 70 | _entitlementsCacheLifetime, 71 | _proxyUrl, 72 | _kidsMode 73 | ); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /lib/src/dto/store_product/product_store_details.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'product_store_details.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | QProductStoreDetails _$QProductStoreDetailsFromJson( 10 | Map json) => 11 | QProductStoreDetails( 12 | json['basePlanId'] as String?, 13 | json['productId'] as String, 14 | json['name'] as String, 15 | json['title'] as String, 16 | json['description'] as String, 17 | QMapper.productOfferDetailsListFromJson(json['subscriptionOfferDetails']), 18 | QMapper.productOfferDetailsFromJson( 19 | json['defaultSubscriptionOfferDetails']), 20 | QMapper.productOfferDetailsFromJson( 21 | json['basePlanSubscriptionOfferDetails']), 22 | QMapper.productInAppDetailsFromJson(json['inAppOfferDetails']), 23 | json['hasTrialOffer'] as bool, 24 | json['hasIntroOffer'] as bool, 25 | json['hasTrialOrIntroOffer'] as bool, 26 | $enumDecode(_$QProductTypeEnumMap, json['productType'], 27 | unknownValue: QProductType.unknown), 28 | json['isInApp'] as bool, 29 | json['isSubscription'] as bool, 30 | json['isPrepaid'] as bool, 31 | json['isInstallment'] as bool, 32 | ); 33 | 34 | Map _$QProductStoreDetailsToJson( 35 | QProductStoreDetails instance) => 36 | { 37 | 'basePlanId': instance.basePlanId, 38 | 'productId': instance.productId, 39 | 'name': instance.name, 40 | 'title': instance.title, 41 | 'description': instance.description, 42 | 'subscriptionOfferDetails': instance.subscriptionOfferDetails, 43 | 'defaultSubscriptionOfferDetails': 44 | instance.defaultSubscriptionOfferDetails, 45 | 'basePlanSubscriptionOfferDetails': 46 | instance.basePlanSubscriptionOfferDetails, 47 | 'inAppOfferDetails': instance.inAppOfferDetails, 48 | 'hasTrialOffer': instance.hasTrialOffer, 49 | 'hasIntroOffer': instance.hasIntroOffer, 50 | 'hasTrialOrIntroOffer': instance.hasTrialOrIntroOffer, 51 | 'productType': _$QProductTypeEnumMap[instance.productType]!, 52 | 'isInApp': instance.isInApp, 53 | 'isSubscription': instance.isSubscription, 54 | 'isPrepaid': instance.isPrepaid, 55 | 'isInstallment': instance.isInstallment, 56 | }; 57 | 58 | const _$QProductTypeEnumMap = { 59 | QProductType.trial: 'Trial', 60 | QProductType.intro: 'Intro', 61 | QProductType.subscription: 'Subscription', 62 | QProductType.inApp: 'InApp', 63 | QProductType.unknown: 'Unknown', 64 | }; 65 | -------------------------------------------------------------------------------- /android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | 14 | 22 | 26 | 30 | 35 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /lib/src/dto/sk_product/sk_product_discount.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | import 'package:qonversion_flutter/src/internal/mapper.dart'; 3 | 4 | import 'discount_payment_mode.dart'; 5 | import 'sk_product.dart'; 6 | import 'subscription_period.dart'; 7 | 8 | part 'sk_product_discount.g.dart'; 9 | 10 | /// Dart wrapper around StoreKit's [SKProductDiscount](https://developer.apple.com/documentation/storekit/skproductdiscount?language=objc). 11 | /// 12 | /// It is used as a property in [SKProduct]. 13 | @JsonSerializable() 14 | class SKProductDiscount { 15 | final String? identifier; 16 | 17 | /// The discounted price, in the currency that is defined in [priceLocale]. 18 | final String price; 19 | 20 | /// Includes locale information about the price, e.g. `$` as the currency symbol for US locale. 21 | @JsonKey(fromJson: QMapper.skPriceLocaleFromJson) 22 | final SKPriceLocale? priceLocale; 23 | 24 | /// The object represent the discount period length. 25 | /// 26 | /// The value must be >= 0. 27 | final int numberOfPeriods; 28 | 29 | /// The object indicates how the discount price is charged. 30 | final SKProductDiscountPaymentMode paymentMode; 31 | 32 | /// The object represents the duration of single subscription period for the discount. 33 | /// 34 | /// The [subscriptionPeriod] of the discount is independent of the product's [subscriptionPeriod], 35 | /// and their units and duration do not have to be matched. 36 | @JsonKey(fromJson: QMapper.skProductSubscriptionPeriodFromJson) 37 | final SKProductSubscriptionPeriod? subscriptionPeriod; 38 | 39 | /// Creates an [SKProductDiscount] with the given discount details. 40 | SKProductDiscount({ 41 | required this.identifier, 42 | required this.price, 43 | required this.priceLocale, 44 | required this.numberOfPeriods, 45 | required this.paymentMode, 46 | required this.subscriptionPeriod, 47 | }); 48 | 49 | /// Constructing an instance from a map from the Objective-C layer. 50 | /// 51 | /// This method should only be used with `map` values returned by [SKProduct.fromJson]. 52 | /// The `map` parameter must not be null. 53 | factory SKProductDiscount.fromJson(Map map) { 54 | return _$SKProductDiscountFromJson(map); 55 | } 56 | 57 | Map toJson() => _$SKProductDiscountToJson(this); 58 | 59 | @override 60 | bool operator ==(Object other) { 61 | if (identical(other, this)) { 62 | return true; 63 | } 64 | if (other.runtimeType != runtimeType) { 65 | return false; 66 | } 67 | return other is SKProductDiscount && 68 | other.identifier == identifier && 69 | other.price == price && 70 | other.priceLocale == priceLocale && 71 | other.numberOfPeriods == numberOfPeriods && 72 | other.paymentMode == paymentMode && 73 | other.subscriptionPeriod == subscriptionPeriod; 74 | } 75 | 76 | @override 77 | int get hashCode => Object.hash(this.identifier, this.price, this.priceLocale, 78 | this.numberOfPeriods, this.paymentMode, this.subscriptionPeriod); 79 | } 80 | -------------------------------------------------------------------------------- /example/lib/params_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:qonversion_flutter/qonversion_flutter.dart'; 3 | 4 | class ParamsView extends StatelessWidget { 5 | @override 6 | Widget build(BuildContext context) { 7 | return Scaffold( 8 | appBar: AppBar( 9 | title: Text('Params Settings'), 10 | ), 11 | body: Padding( 12 | padding: const EdgeInsets.only( 13 | left: 16, 14 | top: 8, 15 | ), 16 | child: Column( 17 | crossAxisAlignment: CrossAxisAlignment.start, 18 | children: [ 19 | TextButton( 20 | child: Text('Get user properties'), 21 | style: ButtonStyle( 22 | backgroundColor: WidgetStateProperty.all(Colors.amber), 23 | foregroundColor: WidgetStateProperty.all(Colors.white), 24 | ), 25 | onPressed: () async { 26 | try { 27 | QUserProperties userProperties = 28 | await Qonversion.getSharedInstance().userProperties(); 29 | userProperties.properties.forEach((userProperty) { 30 | print('User property - key: ' + 31 | userProperty.key + 32 | ', value: ' + 33 | userProperty.value); 34 | }); 35 | } catch (e) { 36 | // handle error here 37 | } 38 | }, 39 | ), 40 | TextButton( 41 | child: Text('Set User ID'), 42 | style: ButtonStyle( 43 | backgroundColor: WidgetStateProperty.all(Colors.green), 44 | foregroundColor: WidgetStateProperty.all(Colors.white), 45 | ), 46 | onPressed: () { 47 | Qonversion.getSharedInstance().setUserProperty(QUserPropertyKey.customUserId, 'customId'); 48 | print('did set user id'); 49 | }, 50 | ), 51 | TextButton( 52 | child: Text('Set User Property'), 53 | style: ButtonStyle( 54 | backgroundColor: WidgetStateProperty.all(Colors.blue), 55 | foregroundColor: WidgetStateProperty.all(Colors.white), 56 | ), 57 | onPressed: () { 58 | Qonversion.getSharedInstance().setCustomUserProperty('customProperty', 'customValue'); 59 | print('did set user property'); 60 | }, 61 | ), 62 | for (final v in QUserPropertyKey.values) 63 | TextButton( 64 | child: Text('Set ${v.name}'), 65 | style: ButtonStyle( 66 | backgroundColor: WidgetStateProperty.all(Colors.purple), 67 | foregroundColor: WidgetStateProperty.all(Colors.white), 68 | ), 69 | onPressed: () { 70 | Qonversion.getSharedInstance().setUserProperty(v, 'email@email.com'); 71 | print('did set property'); 72 | }, 73 | ), 74 | ], 75 | ), 76 | ), 77 | ); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /lib/src/dto/qonversion_error.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'qonversion_error.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | QError _$QErrorFromJson(Map json) => QError( 10 | $enumDecode(_$QErrorCodeEnumMap, json['code'], 11 | unknownValue: QErrorCode.unknown), 12 | json['description'] as String, 13 | json['additionalMessage'] as String?, 14 | ); 15 | 16 | Map _$QErrorToJson(QError instance) => { 17 | 'code': _$QErrorCodeEnumMap[instance.code]!, 18 | 'description': instance.message, 19 | 'additionalMessage': instance.details, 20 | }; 21 | 22 | const _$QErrorCodeEnumMap = { 23 | QErrorCode.unknown: 'Unknown', 24 | QErrorCode.apiRateLimitExceeded: 'ApiRateLimitExceeded', 25 | QErrorCode.appleStoreError: 'AppleStoreError', 26 | QErrorCode.backendError: 'BackendError', 27 | QErrorCode.billingUnavailable: 'BillingUnavailable', 28 | QErrorCode.clientInvalid: 'ClientInvalid', 29 | QErrorCode.cloudServiceNetworkConnectionFailed: 30 | 'CloudServiceNetworkConnectionFailed', 31 | QErrorCode.cloudServicePermissionDenied: 'CloudServicePermissionDenied', 32 | QErrorCode.cloudServiceRevoked: 'CloudServiceRevoked', 33 | QErrorCode.failedToReceiveData: 'FailedToReceiveData', 34 | QErrorCode.featureNotSupported: 'FeatureNotSupported', 35 | QErrorCode.fraudPurchase: 'FraudPurchase', 36 | QErrorCode.incorrectRequest: 'IncorrectRequest', 37 | QErrorCode.internalError: 'InternalError', 38 | QErrorCode.invalidClientUid: 'InvalidClientUid', 39 | QErrorCode.invalidCredentials: 'InvalidCredentials', 40 | QErrorCode.invalidStoreCredentials: 'InvalidStoreCredentials', 41 | QErrorCode.launchError: 'LaunchError', 42 | QErrorCode.networkConnectionFailed: 'NetworkConnectionFailed', 43 | QErrorCode.offeringsNotFound: 'OfferingsNotFound', 44 | QErrorCode.offeringsNotAvailable: 'OfferingsNotAvailable', 45 | QErrorCode.paymentInvalid: 'PaymentInvalid', 46 | QErrorCode.paymentNotAllowed: 'PaymentNotAllowed', 47 | QErrorCode.playStoreError: 'PlayStoreError', 48 | QErrorCode.privacyAcknowledgementRequired: 'PrivacyAcknowledgementRequired', 49 | QErrorCode.productAlreadyOwned: 'ProductAlreadyOwned', 50 | QErrorCode.productNotFound: 'ProductNotFound', 51 | QErrorCode.productNotOwned: 'ProductNotOwned', 52 | QErrorCode.projectConfigError: 'ProjectConfigError', 53 | QErrorCode.purchaseCanceled: 'PurchaseCanceled', 54 | QErrorCode.purchaseInvalid: 'PurchaseInvalid', 55 | QErrorCode.purchasePending: 'PurchasePending', 56 | QErrorCode.purchaseUnspecified: 'PurchaseUnspecified', 57 | QErrorCode.receiptValidationError: 'ReceiptValidationError', 58 | QErrorCode.remoteConfigurationNotAvailable: 'RemoteConfigurationNotAvailable', 59 | QErrorCode.responseParsingFailed: 'ResponseParsingFailed', 60 | QErrorCode.storeProductNotAvailable: 'StoreProductNotAvailable', 61 | QErrorCode.unauthorizedRequestData: 'UnauthorizedRequestData', 62 | QErrorCode.unknownClientPlatform: 'UnknownClientPlatform', 63 | }; 64 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "com.android.application" 3 | id "kotlin-android" 4 | id "dev.flutter.flutter-gradle-plugin" 5 | id "com.google.gms.google-services" 6 | } 7 | 8 | def localProperties = new Properties() 9 | def localPropertiesFile = rootProject.file('local.properties') 10 | if (localPropertiesFile.exists()) { 11 | localPropertiesFile.withReader('UTF-8') { reader -> 12 | localProperties.load(reader) 13 | } 14 | } 15 | 16 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 17 | if (flutterVersionCode == null) { 18 | flutterVersionCode = '1' 19 | } 20 | 21 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 22 | if (flutterVersionName == null) { 23 | flutterVersionName = '1.0.0' 24 | } 25 | 26 | android { 27 | compileSdk 34 28 | 29 | sourceSets { 30 | main.java.srcDirs += 'src/main/kotlin' 31 | } 32 | 33 | defaultConfig { 34 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 35 | applicationId "io.qonversion.sampleapp" 36 | minSdkVersion 21 37 | targetSdkVersion 34 38 | versionCode flutterVersionCode.toInteger() 39 | versionName flutterVersionName 40 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 41 | multiDexEnabled true 42 | } 43 | 44 | signingConfigs { 45 | release { 46 | File propertiesFile = project.rootProject.file('local.properties') 47 | if (propertiesFile.exists()) { 48 | Properties properties = new Properties() 49 | properties.load(propertiesFile.newDataInputStream()) 50 | storeFile file(properties.getProperty('storeFile')) 51 | keyAlias properties.getProperty('keyAlias') 52 | storePassword properties.getProperty('storePassword') 53 | keyPassword properties.getProperty('keyPassword') 54 | } 55 | } 56 | } 57 | 58 | buildTypes { 59 | release { 60 | signingConfig signingConfigs.release 61 | } 62 | debug { 63 | signingConfig signingConfigs.release 64 | debuggable true 65 | } 66 | } 67 | compileOptions { 68 | sourceCompatibility JavaVersion.VERSION_11 69 | targetCompatibility JavaVersion.VERSION_11 70 | } 71 | kotlinOptions { 72 | jvmTarget = '11' 73 | } 74 | lint { 75 | disable 'InvalidPackage' 76 | } 77 | namespace 'io.qonversion.sampleapp' 78 | } 79 | 80 | flutter { 81 | source '../..' 82 | } 83 | 84 | dependencies { 85 | testImplementation 'junit:junit:4.12' 86 | androidTestImplementation 'androidx.test:runner:1.1.1' 87 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' 88 | implementation 'androidx.multidex:multidex:2.0.1' 89 | // implementation project(':qonversion_flutter') 90 | } 91 | 92 | // Задача wrapper для совместимости с Android Studio 93 | tasks.register("wrapper") { 94 | doLast { 95 | println "Wrapper task is only available in root project. Use './gradlew wrapper' from root." 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /lib/src/dto/store_product/product_offer_details.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | import 'package:qonversion_flutter/src/dto/store_product/product_installment_plan_details.dart'; 3 | import 'package:qonversion_flutter/src/internal/mapper.dart'; 4 | 5 | import './product_pricing_phase.dart'; 6 | 7 | part 'product_offer_details.g.dart'; 8 | 9 | /// This class contains all the information about the Google subscription offer details. 10 | /// It might be either a plain base plan details or a base plan with the concrete offer details. 11 | @JsonSerializable() 12 | class QProductOfferDetails { 13 | /// The identifier of the current base plan. 14 | @JsonKey(name: 'basePlanId') 15 | final String basePlanId; 16 | 17 | /// The identifier of the concrete offer, to which these details belong. 18 | /// Null, if these are plain base plan details. 19 | @JsonKey(name: 'offerId') 20 | final String? offerId; 21 | 22 | /// A token to purchase the current offer. 23 | @JsonKey(name: 'offerToken') 24 | final String offerToken; 25 | 26 | /// List of tags set for the current offer. 27 | @JsonKey(name: 'tags') 28 | final List tags; 29 | 30 | /// A time-ordered list of pricing phases for the current offer. 31 | @JsonKey(name: 'pricingPhases', fromJson: QMapper.productPricingPhaseListFromJson) 32 | final List pricingPhases; 33 | 34 | /// A base plan phase details. 35 | @JsonKey(name: 'basePlan', fromJson: QMapper.productPricingPhaseFromJson) 36 | final QProductPricingPhase? basePlan; 37 | 38 | /// Additional details of an installment plan, if exists. 39 | @JsonKey(name: 'installmentPlanDetails', fromJson: QMapper.productInstallmentPlanDetailsFromJson) 40 | final QProductInstallmentPlanDetails? installmentPlanDetails; 41 | 42 | /// A trial phase details, if exists. 43 | @JsonKey(name: 'introPhase', fromJson: QMapper.productPricingPhaseFromJson) 44 | final QProductPricingPhase? introPhase; 45 | 46 | /// The intro phase details, if exists. 47 | /// Intro phase is one of single or recurrent discounted payments. 48 | @JsonKey(name: 'trialPhase', fromJson: QMapper.productPricingPhaseFromJson) 49 | final QProductPricingPhase? trialPhase; 50 | 51 | /// True, if there is a trial phase in the current offer. False otherwise. 52 | @JsonKey(name: 'hasTrial') 53 | final bool hasTrial; 54 | 55 | /// True, if there is any intro phase in the current offer. False otherwise. 56 | /// The intro phase is one of single or recurrent discounted payments. 57 | @JsonKey(name: 'hasIntro') 58 | final bool hasIntro; 59 | 60 | /// True, if there is any trial or intro phase in the current offer. False otherwise. 61 | /// The intro phase is one of single or recurrent discounted payments. 62 | @JsonKey(name: 'hasTrialOrIntro') 63 | final bool hasTrialOrIntro; 64 | 65 | const QProductOfferDetails( 66 | this.basePlanId, 67 | this.offerId, 68 | this.offerToken, 69 | this.tags, 70 | this.pricingPhases, 71 | this.basePlan, 72 | this.installmentPlanDetails, 73 | this.introPhase, 74 | this.trialPhase, 75 | this.hasTrial, 76 | this.hasIntro, 77 | this.hasTrialOrIntro, 78 | ); 79 | 80 | factory QProductOfferDetails.fromJson(Map json) => 81 | _$QProductOfferDetailsFromJson(json); 82 | 83 | Map toJson() => _$QProductOfferDetailsToJson(this); 84 | } -------------------------------------------------------------------------------- /lib/src/dto/store_product/product_pricing_phase.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | import 'package:qonversion_flutter/src/internal/mapper.dart'; 3 | import '../subscription_period.dart'; 4 | import 'product_price.dart'; 5 | 6 | part 'product_pricing_phase.g.dart'; 7 | 8 | /// Recurrence mode of the pricing phase. 9 | enum QPricingPhaseRecurrenceMode { 10 | /// The billing plan payment recurs for infinite billing periods unless canceled. 11 | @JsonValue("InfiniteRecurring") 12 | infiniteRecurring, 13 | 14 | /// The billing plan payment recurs for a fixed number of billing periods 15 | /// set in [QProductPricingPhase.billingCycleCount]. 16 | @JsonValue("FiniteRecurring") 17 | finiteRecurring, 18 | 19 | /// The billing plan payment is a one-time charge that does not repeat. 20 | @JsonValue("NonRecurring") 21 | nonRecurring, 22 | 23 | /// Unknown recurrence mode. 24 | @JsonValue("Unknown") 25 | unknown 26 | } 27 | 28 | /// Type of the pricing phase. 29 | enum QPricingPhaseType { 30 | /// Regular subscription without any discounts like trial or intro offers. 31 | @JsonValue("Regular") 32 | regular, 33 | 34 | /// A free phase. 35 | @JsonValue("FreeTrial") 36 | freeTrial, 37 | 38 | /// A phase with a discounted payment for a single period. 39 | @JsonValue("DiscountedSinglePayment") 40 | discountedSinglePayment, 41 | 42 | /// A phase with a discounted payment for several periods, described in [QProductPricingPhase.billingCycleCount]. 43 | @JsonValue("DiscountedRecurringPayment") 44 | discountedRecurringPayment, 45 | 46 | /// Unknown pricing phase type 47 | @JsonValue("Unknown") 48 | unknown 49 | } 50 | 51 | /// This class represents a pricing phase, describing how a user pays at a point in time. 52 | @JsonSerializable() 53 | class QProductPricingPhase { 54 | /// Price for the current phase. 55 | @JsonKey(name: 'price', fromJson: QMapper.requiredProductPriceFromJson) 56 | final QProductPrice price; 57 | 58 | /// The billing period for which the given price applies. 59 | @JsonKey(name: 'billingPeriod', fromJson: QMapper.requiredSubscriptionPeriodFromJson) 60 | final QSubscriptionPeriod billingPeriod; 61 | 62 | /// Number of cycles for which the billing period is applied. 63 | @JsonKey(name: 'billingCycleCount') 64 | final int billingCycleCount; 65 | 66 | /// Recurrence mode for the pricing phase. 67 | @JsonKey(name: 'recurrenceMode', unknownEnumValue: QPricingPhaseRecurrenceMode.unknown) 68 | final QPricingPhaseRecurrenceMode recurrenceMode; 69 | 70 | /// Type of the pricing phase. 71 | @JsonKey(name: 'type', unknownEnumValue: QPricingPhaseType.unknown) 72 | final QPricingPhaseType type; 73 | 74 | /// True, if the current phase is a trial period. False otherwise. 75 | @JsonKey(name: 'isTrial') 76 | final bool isTrial; 77 | 78 | /// True, if the current phase is an intro period. False otherwise. 79 | /// The intro phase is one of single or recurrent discounted payments. 80 | @JsonKey(name: 'isIntro') 81 | final bool isIntro; 82 | 83 | /// True, if the current phase represents the base plan. False otherwise. 84 | @JsonKey(name: 'isBasePlan') 85 | final bool isBasePlan; 86 | 87 | const QProductPricingPhase( 88 | this.price, 89 | this.billingPeriod, 90 | this.billingCycleCount, 91 | this.recurrenceMode, 92 | this.type, 93 | this.isTrial, 94 | this.isIntro, 95 | this.isBasePlan, 96 | ); 97 | 98 | factory QProductPricingPhase.fromJson(Map json) => 99 | _$QProductPricingPhaseFromJson(json); 100 | 101 | Map toJson() => _$QProductPricingPhaseToJson(this); 102 | } -------------------------------------------------------------------------------- /lib/src/dto/user_properties.dart: -------------------------------------------------------------------------------- 1 | import 'package:collection/collection.dart' show IterableExtension; 2 | import 'package:json_annotation/json_annotation.dart'; 3 | import 'package:qonversion_flutter/qonversion_flutter.dart'; 4 | 5 | part 'user_properties.g.dart'; 6 | 7 | @JsonSerializable(createToJson: false) 8 | class QUserProperties { 9 | /// List of all user properties. 10 | @JsonKey(name: "properties") 11 | final List properties; 12 | 13 | /// List of user properties, set for the Qonversion defined keys. 14 | /// This is a subset of all [properties] list. 15 | /// See [Qonversion.setUserProperty]. 16 | final List definedProperties; 17 | 18 | /// List of user properties, set for custom keys. 19 | /// This is a subset of all [properties] list. 20 | /// See [Qonversion.setCustomUserProperty]. 21 | final List customProperties; 22 | 23 | /// Map of all user properties. 24 | /// This is a flattened version of the [properties] list as a key-value map. 25 | final Map flatPropertiesMap; 26 | 27 | /// Map of user properties, set for the Qonversion defined keys. 28 | /// This is a flattened version of the [definedProperties] list as a key-value map. 29 | /// See [Qonversion.setUserProperty]. 30 | final Map flatDefinedPropertiesMap; 31 | 32 | /// Map of user properties, set for custom keys. 33 | /// This is a flattened version of the [customProperties] list as a key-value map. 34 | /// See [Qonversion.setCustomUserProperty]. 35 | final Map flatCustomPropertiesMap; 36 | 37 | QUserProperties._( 38 | this.properties, 39 | this.definedProperties, 40 | this.customProperties, 41 | this.flatPropertiesMap, 42 | this.flatDefinedPropertiesMap, 43 | this.flatCustomPropertiesMap, 44 | ); 45 | 46 | factory QUserProperties(List properties) { 47 | final List definedProperties = properties.whereNot( 48 | (userProperty) => userProperty.definedKey == QUserPropertyKey.custom 49 | ).toList(); 50 | final List customProperties = properties.where( 51 | (userProperty) => userProperty.definedKey == QUserPropertyKey.custom 52 | ).toList(); 53 | 54 | final Map flatPropertiesMap = Map.fromIterable( 55 | properties, 56 | key: (userProperty) => userProperty.key, 57 | value: (userProperty) => userProperty.value, 58 | ); 59 | 60 | final Map flatDefinedPropertiesMap = Map.fromIterable( 61 | definedProperties, 62 | key: (userProperty) => userProperty.definedKey, 63 | value: (userProperty) => userProperty.value, 64 | ); 65 | 66 | final Map flatCustomPropertiesMap = Map.fromIterable( 67 | customProperties, 68 | key: (userProperty) => userProperty.key, 69 | value: (userProperty) => userProperty.value, 70 | ); 71 | 72 | return QUserProperties._( 73 | properties, 74 | definedProperties, 75 | customProperties, 76 | flatPropertiesMap, 77 | flatDefinedPropertiesMap, 78 | flatCustomPropertiesMap, 79 | ); 80 | } 81 | 82 | factory QUserProperties.fromJson(Map json) => 83 | _$QUserPropertiesFromJson(json); 84 | 85 | /// Searches for a property with the given property [key] in all properties list. 86 | QUserProperty? getProperty(String key) { 87 | return properties.firstWhereOrNull((userProperty) => userProperty.key == key); 88 | } 89 | 90 | /// Searches for a property with the given Qonversion defined property [key] 91 | /// in defined properties list. 92 | QUserProperty? getDefinedProperty(QUserPropertyKey key) { 93 | return definedProperties.firstWhereOrNull((userProperty) => userProperty.definedKey == key); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 57 | 59 | 65 | 66 | 67 | 68 | 69 | 70 | 76 | 78 | 84 | 85 | 86 | 87 | 89 | 90 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /fastlane/Fastfile: -------------------------------------------------------------------------------- 1 | def update_dart(new_version) 2 | path = Dir['../lib/**/qonversion_internal.dart'].first 3 | regex = /static const String sdkVersion = ".*";/ 4 | result_value = "static const String sdkVersion = \"#{new_version}\";" 5 | 6 | update_file(path, regex, result_value) 7 | end 8 | 9 | def update_yaml(new_version) 10 | path = "../pubspec.yaml" 11 | regex = /version: .*/ 12 | result_value = "version: #{new_version}" 13 | 14 | update_file(path, regex, result_value) 15 | end 16 | 17 | def update_file(path, regex, result_value) 18 | file = File.read(path) 19 | new_content = file.gsub(regex, result_value) 20 | File.open(path, 'w') { |line| line.puts new_content } 21 | end 22 | 23 | def update_changelog(new_version) 24 | changelog_update = "\#\# #{new_version}\n* // Update changelog here\n\n" 25 | path = "../CHANGELOG.md" 26 | file = File.read(path) 27 | File.open(path, 'w') { |line| 28 | line.puts changelog_update + file 29 | } 30 | end 31 | 32 | def upgrade_sandwich_android(new_version) 33 | path = "../android/build.gradle" 34 | common_part = "implementation \"io.qonversion:sandwich:" 35 | regex = /#{common_part}.*"/ 36 | result_value = "#{common_part}#{new_version}\"" 37 | 38 | update_file(path, regex, result_value) 39 | end 40 | 41 | def upgrade_sandwich_apple(platform, new_version) 42 | path = "../#{platform}/qonversion_flutter.podspec" 43 | common_part = "s.dependency \"QonversionSandwich\", \"" 44 | regex = /#{common_part}.*"/ 45 | result_value = "#{common_part}#{new_version}\"" 46 | 47 | update_file(path, regex, result_value) 48 | end 49 | 50 | def update_file(path, regex, result_value) 51 | file = File.read(path) 52 | new_content = file.gsub(regex, result_value) 53 | File.open(path, 'w') { |line| line.puts new_content } 54 | end 55 | 56 | def get_tag 57 | tag = last_git_tag() 58 | puts tag 59 | result_tag = tag.scan(%r{\d{1,2}.\d{1,2}.\d{1,3}}).first 60 | return result_tag 61 | end 62 | 63 | def calculate_minor_version(tag) 64 | major, minor, patch = parse_versions(tag) 65 | new_minor_version = minor.to_i.next.to_s 66 | new_version = major + "." + new_minor_version + "." + "0" 67 | return new_version 68 | end 69 | 70 | def calculate_patch_version(tag) 71 | major, minor, patch = parse_versions(tag) 72 | new_patch_version = patch.to_i.next.to_s 73 | new_version = major + "." + minor + "." + new_patch_version 74 | 75 | return new_version 76 | end 77 | 78 | def push_tag(tag) 79 | system("git checkout develop") 80 | system("git pull origin develop") 81 | add_git_tag(tag: tag) 82 | push_git_tags(tag: tag) 83 | end 84 | 85 | def parse_versions(tag) 86 | split_version_array = tag.split(".", 3) 87 | 88 | if split_version_array.length == 3 89 | major = split_version_array[0] 90 | minor = split_version_array[1] 91 | patch = split_version_array[2] 92 | 93 | return major, minor, patch 94 | end 95 | end 96 | 97 | lane :patch do 98 | tag = get_tag 99 | new_version = calculate_patch_version(tag) 100 | new_tag = "prerelease/" + new_version 101 | push_tag(new_tag) 102 | end 103 | 104 | lane :minor do 105 | tag = get_tag 106 | new_version = calculate_minor_version(tag) 107 | new_tag = "prerelease/" + new_version 108 | push_tag(new_tag) 109 | end 110 | 111 | lane :bump do |options| 112 | new_version = options[:version] 113 | 114 | update_dart(new_version) 115 | update_yaml(new_version) 116 | update_changelog(new_version) 117 | end 118 | 119 | lane :upgrade_sandwich do |options| 120 | new_version = options[:version] 121 | 122 | upgrade_sandwich_android(new_version) 123 | upgrade_sandwich_apple("ios", new_version) 124 | upgrade_sandwich_apple("macos", new_version) 125 | end 126 | 127 | lane :provide_next_patch_version do 128 | tag = get_tag 129 | new_version = calculate_patch_version(tag) 130 | sh("echo version=#{new_version} >> \"$GITHUB_ENV\"") 131 | end 132 | -------------------------------------------------------------------------------- /lib/src/dto/purchase_options_builder.dart: -------------------------------------------------------------------------------- 1 | import 'product.dart'; 2 | import 'promotional_offer.dart'; 3 | import 'purchase_options.dart'; 4 | import 'purchase_update_policy.dart'; 5 | import 'store_product/product_offer_details.dart'; 6 | import 'store_product/product_store_details.dart'; 7 | 8 | class QPurchaseOptionsBuilder { 9 | String? _offerId; 10 | bool _applyOffer = true; 11 | QProduct? _oldProduct; 12 | QPurchaseUpdatePolicy? _updatePolicy; 13 | List? _contextKeys; 14 | int _quantity = 1; 15 | QPromotionalOffer? _promotionalOffer; 16 | 17 | /// Android only. 18 | /// Set the offer to the purchase. 19 | /// If [offer] is not specified, then the default offer will be applied. To know how we choose 20 | /// the default offer, see [QProductStoreDetails.defaultSubscriptionOfferDetails]. 21 | /// 22 | /// [offer] concrete offer which you'd like to purchase. 23 | /// Returns builder instance for chain calls. 24 | QPurchaseOptionsBuilder setOffer(QProductOfferDetails offer) { 25 | _offerId = offer.offerId; 26 | return this; 27 | } 28 | 29 | /// Android only. 30 | /// Set the offer Id to the purchase. 31 | /// If [offerId] is not specified, then the default offer will be applied. To know how we choose 32 | /// the default offer, see [QProductStoreDetails.defaultSubscriptionOfferDetails]. 33 | /// 34 | /// [offerId] concrete offer Id which you'd like to purchase. 35 | /// Returns builder instance for chain calls. 36 | QPurchaseOptionsBuilder setOfferId(String offerId) { 37 | _offerId = offerId; 38 | return this; 39 | } 40 | 41 | /// Android only. 42 | /// Call this function to remove any intro/trial offer from the purchase (use only a bare base plan). 43 | /// Returns builder instance for chain calls. 44 | QPurchaseOptionsBuilder removeOffer() { 45 | _applyOffer = false; 46 | return this; 47 | } 48 | 49 | /// Android only. 50 | /// Set Qonversion product from which the upgrade/downgrade 51 | /// will be initialized. 52 | /// 53 | /// [oldProduct] Qonversion product from which the upgrade/downgrade 54 | /// will be initialized. 55 | /// Returns builder instance for chain calls. 56 | QPurchaseOptionsBuilder setOldProduct(QProduct oldProduct) { 57 | _oldProduct = oldProduct; 58 | return this; 59 | } 60 | 61 | /// Android only. 62 | /// Set the update policy for the purchase. 63 | /// If the [updatePolicy] is not provided, then default one 64 | /// will be selected - [QPurchaseUpdatePolicy.withTimeProration]. 65 | /// [updatePolicy] update policy for the purchase. 66 | /// Returns builder instance for chain calls. 67 | QPurchaseOptionsBuilder setUpdatePolicy(QPurchaseUpdatePolicy updatePolicy) { 68 | _updatePolicy = updatePolicy; 69 | return this; 70 | } 71 | 72 | /// Set the context keys associated with a purchase. 73 | /// 74 | /// [contextKeys] context keys for the purchase. 75 | /// Returns builder instance for chain calls. 76 | QPurchaseOptionsBuilder setContextKeys(List contextKeys) { 77 | _contextKeys = contextKeys; 78 | return this; 79 | } 80 | 81 | /// iOS only. 82 | /// Set quantity of product purchasing. Use for consumable in-app products. 83 | /// 84 | /// [quantity] quantity of product purchasing. 85 | /// Returns builder instance for chain calls. 86 | QPurchaseOptionsBuilder setQuantity(int quantity) { 87 | _quantity = quantity; 88 | return this; 89 | } 90 | 91 | /// Set the promotional offer details. 92 | /// 93 | /// [promotionalOffer] promotional offer details. 94 | /// Returns builder instance for chain calls. 95 | QPurchaseOptionsBuilder setPromotionalOffer(QPromotionalOffer promotionalOffer) { 96 | _promotionalOffer = promotionalOffer; 97 | return this; 98 | } 99 | 100 | /// Generate [QPurchaseOptions] instance with all the provided options. 101 | /// 102 | /// Returns the complete [QPurchaseOptions] instance. 103 | QPurchaseOptions build() { 104 | return QPurchaseOptions(_offerId, _applyOffer, _oldProduct, _updatePolicy, _contextKeys, _quantity, _promotionalOffer); 105 | } 106 | 107 | } -------------------------------------------------------------------------------- /lib/src/nocodes.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dto/nocodes_events.dart'; 3 | import 'dto/presentation_config.dart'; 4 | import 'nocodes_config.dart'; 5 | import 'internal/nocodes_internal.dart'; 6 | 7 | /// Main No-Codes API class 8 | /// 9 | /// **Platform Support:** 10 | /// - ✅ iOS: Full support 11 | /// - ✅ Android: Full support 12 | /// - ❌ macOS: Not supported (returns empty streams and no-op methods) 13 | abstract class NoCodes { 14 | static NoCodes? _backingInstance; 15 | 16 | /// Use this variable to get a current initialized instance of the No-Codes SDK. 17 | /// Please, use the property only after calling [NoCodes.initialize]. 18 | /// Otherwise, trying to access the variable will cause an exception. 19 | /// 20 | /// Returns current initialized instance of the No-Codes SDK. 21 | /// Throws exception if the instance has not been initialized 22 | static NoCodes getSharedInstance() { 23 | NoCodes? instance = _backingInstance; 24 | 25 | if (instance == null) { 26 | throw new Exception("NoCodes has not been initialized. You should call " + 27 | "the initialize method before accessing the shared instance of NoCodes."); 28 | } 29 | 30 | return instance; 31 | } 32 | 33 | /// An entry point to use No-Codes SDK. Call to initialize No-Codes SDK with required config. 34 | /// The function is the best way to set additional configs you need to use No-Codes SDK. 35 | /// 36 | /// **Platform Support:** iOS and Android. On macOS, this will initialize but functionality will be limited. 37 | /// 38 | /// [config] a config that contains key SDK settings. 39 | /// Call [NoCodesConfigBuilder.build] to configure and create a [NoCodesConfig] instance. 40 | /// Returns initialized instance of the No-Codes SDK. 41 | static NoCodes initialize(NoCodesConfig config) { 42 | NoCodes instance = NoCodesInternal(config); 43 | _backingInstance = instance; 44 | return instance; 45 | } 46 | 47 | /// Initialize No-Codes with project key (for backward compatibility) 48 | /// 49 | /// **Platform Support:** iOS and Android. On macOS, this will initialize but functionality will be limited. 50 | static Future initializeWithProjectKey(String projectKey) async { 51 | final config = NoCodesConfig(projectKey); 52 | initialize(config); 53 | } 54 | 55 | /// Stream of screen shown events 56 | /// 57 | /// **Platform Support:** iOS and Android. Returns empty stream on macOS. 58 | Stream get screenShownStream; 59 | 60 | /// Stream of finished events 61 | /// 62 | /// **Platform Support:** iOS and Android. Returns empty stream on macOS. 63 | Stream get finishedStream; 64 | 65 | /// Stream of action started events 66 | /// 67 | /// **Platform Support:** iOS and Android. Returns empty stream on macOS. 68 | Stream get actionStartedStream; 69 | 70 | /// Stream of action failed events 71 | /// 72 | /// **Platform Support:** iOS and Android. Returns empty stream on macOS. 73 | Stream get actionFailedStream; 74 | 75 | /// Stream of action finished events 76 | /// 77 | /// **Platform Support:** iOS and Android. Returns empty stream on macOS. 78 | Stream get actionFinishedStream; 79 | 80 | /// Stream of screen failed to load events 81 | /// 82 | /// **Platform Support:** iOS and Android. Returns empty stream on macOS. 83 | Stream get screenFailedToLoadStream; 84 | 85 | /// Set screen presentation configuration 86 | /// 87 | /// **Platform Support:** iOS and Android. No-op on macOS. 88 | Future setScreenPresentationConfig( 89 | NoCodesPresentationConfig config, { 90 | String? contextKey, 91 | }); 92 | 93 | /// Show No-Codes screen with context key 94 | /// 95 | /// **Platform Support:** iOS and Android. No-op on macOS. 96 | Future showScreen(String contextKey); 97 | 98 | /// Close No-Codes screen 99 | /// 100 | /// **Platform Support:** iOS and Android. No-op on macOS. 101 | Future close(); 102 | 103 | } -------------------------------------------------------------------------------- /lib/src/internal/constants.dart: -------------------------------------------------------------------------------- 1 | class Constants { 2 | // Params names 3 | static const kProjectKey = 'projectKey'; 4 | static const kLaunchMode = 'launchMode'; 5 | static const kEnvironment = 'environment'; 6 | static const kEntitlementsCacheLifetime = 'entitlementsCacheLifetime'; 7 | static const kProxyUrl = 'proxyUrl'; 8 | static const kKidsMode = 'kidsMode'; 9 | static const kUserId = 'userId'; 10 | static const kData = 'data'; 11 | static const kProvider = 'provider'; 12 | static const kDetails = 'details'; 13 | static const kProductId = 'productId'; 14 | static const kOfferId = 'offerId'; 15 | static const kApplyOffer = 'applyOffer'; 16 | static const kOfferingId = 'offeringId'; 17 | static const kOldProductId = 'oldProductId'; 18 | static const kUpdatePolicyKey = 'updatePolicyKey'; 19 | static const kError = 'error'; 20 | static const kIsCancelled = 'is_cancelled'; 21 | static const kEntitlements = 'entitlements'; 22 | static const kProperty = 'property'; 23 | static const kValue = 'value'; 24 | static const kLifetime = 'lifetime'; 25 | static const kExperimentId = 'experimentId'; 26 | static const kGroupId = 'groupId'; 27 | static const kRemoteConfigurationId = 'remoteConfigurationId'; 28 | static const kContextKey = 'contextKey'; 29 | static const kContextKeys = 'contextKeys'; 30 | static const kPurchaseContextKeys = 'contextKeys'; 31 | static const kPurchaseQuantity = 'quantity'; 32 | static const kIncludeEmptyContextKey = 'includeEmptyContextKey'; 33 | static const kDiscountId = 'discountId'; 34 | static const kPromoOffer = 'promoOffer'; 35 | static const kConfig = 'config'; 36 | static const kVersion = 'version'; 37 | static const kSource = 'source'; 38 | 39 | // MethodChannel methods names 40 | static const mInitialize = 'initialize'; 41 | static const mSyncHistoricalData = 'syncHistoricalData'; 42 | static const mSyncStoreKit2Purchases = 'syncStoreKit2Purchases'; 43 | static const mProducts = 'products'; 44 | static const mGetPromotionalOffer = 'getPromotionalOffer'; 45 | static const mPurchase = 'purchase'; 46 | static const mPromoPurchase = 'promoPurchase'; 47 | static const mUpdatePurchase = 'updatePurchase'; 48 | static const mCheckEntitlements = 'checkEntitlements'; 49 | static const mRestore = 'restore'; 50 | static const mSetDefinedUserProperty = 'setDefinedUserProperty'; 51 | static const mSetCustomUserProperty = 'setCustomUserProperty'; 52 | static const mUserProperties = 'userProperties'; 53 | static const mSetEntitlementsCacheLifetime = 'setEntitlementsCacheLifetime'; 54 | static const mSyncPurchases = 'syncPurchases'; 55 | static const mAddAttributionData = 'addAttributionData'; 56 | static const mSetDebugMode = 'setDebugMode'; 57 | static const mCollectAdvertisingId = 'collectAdvertisingId'; 58 | static const mIsFallbackFileAccessible = 'isFallbackFileAccessible'; 59 | static const mOfferings = 'offerings'; 60 | static const mCheckTrialIntroEligibility = 'checkTrialIntroEligibility'; 61 | static const mStoreSdkInfo = 'storeSdkInfo'; 62 | static const mIdentify = 'identify'; 63 | static const mLogout = 'logout'; 64 | static const mUserInfo = 'userInfo'; 65 | static const mRemoteConfig = 'remoteConfig'; 66 | static const mRemoteConfigList = 'remoteConfigList'; 67 | static const mRemoteConfigListForContextKeys = 'remoteConfigListForContextKeys'; 68 | static const mAttachUserToExperiment = 'attachUserToExperiment'; 69 | static const mDetachUserFromExperiment = 'detachUserFromExperiment'; 70 | static const mAttachUserToRemoteConfiguration = 'attachUserToRemoteConfiguration'; 71 | static const mDetachUserFromRemoteConfiguration = 'detachUserFromRemoteConfiguration'; 72 | static const mCollectAppleSearchAdsAttribution = 'collectAppleSearchAdsAttribution'; 73 | static const mPresentCodeRedemptionSheet = 'presentCodeRedemptionSheet'; 74 | static const mInitializeNoCodes = 'initializeNoCodes'; 75 | static const mSetScreenPresentationConfig = 'setScreenPresentationConfig'; 76 | static const mShowNoCodesScreen = 'showNoCodesScreen'; 77 | static const mCloseNoCodes = 'closeNoCodes'; 78 | 79 | // Other constants 80 | static const skuDetailsPriceRatio = 1000000; 81 | static const sdkSource = "flutter"; 82 | } 83 | -------------------------------------------------------------------------------- /lib/src/dto/nocodes_events.dart: -------------------------------------------------------------------------------- 1 | /// Base class for all No-Codes events 2 | abstract class NoCodesEvent { 3 | const NoCodesEvent(); 4 | } 5 | 6 | /// Event when No-Codes screen is shown 7 | class NoCodesScreenShownEvent extends NoCodesEvent { 8 | final Map? payload; 9 | 10 | const NoCodesScreenShownEvent({this.payload}); 11 | 12 | factory NoCodesScreenShownEvent.fromMap(Map map) { 13 | return NoCodesScreenShownEvent( 14 | payload: map['payload'] as Map?, 15 | ); 16 | } 17 | 18 | Map toMap() { 19 | return { 20 | 'type': 'nocodes_screen_shown', 21 | 'payload': payload, 22 | }; 23 | } 24 | 25 | @override 26 | String toString() { 27 | return 'NoCodesScreenShownEvent(payload: $payload)'; 28 | } 29 | } 30 | 31 | /// Event when NoCodes flow is finished 32 | class NoCodesFinishedEvent extends NoCodesEvent { 33 | final Map? payload; 34 | 35 | const NoCodesFinishedEvent({this.payload}); 36 | 37 | factory NoCodesFinishedEvent.fromMap(Map map) { 38 | return NoCodesFinishedEvent( 39 | payload: map['payload'] as Map?, 40 | ); 41 | } 42 | 43 | Map toMap() { 44 | return { 45 | 'type': 'nocodes_finished', 46 | 'payload': payload, 47 | }; 48 | } 49 | 50 | @override 51 | String toString() { 52 | return 'NoCodesFinishedEvent(payload: $payload)'; 53 | } 54 | } 55 | 56 | /// Event when NoCodes action is started 57 | class NoCodesActionStartedEvent extends NoCodesEvent { 58 | final Map? payload; 59 | 60 | const NoCodesActionStartedEvent({this.payload}); 61 | 62 | factory NoCodesActionStartedEvent.fromMap(Map map) { 63 | return NoCodesActionStartedEvent( 64 | payload: map['payload'] as Map?, 65 | ); 66 | } 67 | 68 | Map toMap() { 69 | return { 70 | 'type': 'nocodes_action_started', 71 | 'payload': payload, 72 | }; 73 | } 74 | 75 | @override 76 | String toString() { 77 | return 'NoCodesActionStartedEvent(payload: $payload)'; 78 | } 79 | } 80 | 81 | /// Event when NoCodes action failed 82 | class NoCodesActionFailedEvent extends NoCodesEvent { 83 | final Map? payload; 84 | 85 | const NoCodesActionFailedEvent({this.payload}); 86 | 87 | factory NoCodesActionFailedEvent.fromMap(Map map) { 88 | return NoCodesActionFailedEvent( 89 | payload: map['payload'] as Map?, 90 | ); 91 | } 92 | 93 | Map toMap() { 94 | return { 95 | 'type': 'nocodes_action_failed', 96 | 'payload': payload, 97 | }; 98 | } 99 | 100 | @override 101 | String toString() { 102 | return 'NoCodesActionFailedEvent(payload: $payload)'; 103 | } 104 | } 105 | 106 | /// Event when NoCodes action is finished 107 | class NoCodesActionFinishedEvent extends NoCodesEvent { 108 | final Map? payload; 109 | 110 | const NoCodesActionFinishedEvent({this.payload}); 111 | 112 | factory NoCodesActionFinishedEvent.fromMap(Map map) { 113 | return NoCodesActionFinishedEvent( 114 | payload: map['payload'] as Map?, 115 | ); 116 | } 117 | 118 | Map toMap() { 119 | return { 120 | 'type': 'nocodes_action_finished', 121 | 'payload': payload, 122 | }; 123 | } 124 | 125 | @override 126 | String toString() { 127 | return 'NoCodesActionFinishedEvent(payload: $payload)'; 128 | } 129 | } 130 | 131 | /// Event when NoCodes screen failed to load 132 | class NoCodesScreenFailedToLoadEvent extends NoCodesEvent { 133 | final Map? payload; 134 | 135 | const NoCodesScreenFailedToLoadEvent({this.payload}); 136 | 137 | factory NoCodesScreenFailedToLoadEvent.fromMap(Map map) { 138 | return NoCodesScreenFailedToLoadEvent( 139 | payload: map['payload'] as Map?, 140 | ); 141 | } 142 | 143 | Map toMap() { 144 | return { 145 | 'type': 'nocodes_screen_failed_to_load', 146 | 'payload': payload, 147 | }; 148 | } 149 | 150 | @override 151 | String toString() { 152 | return 'NoCodesScreenFailedToLoadEvent(payload: $payload)'; 153 | } 154 | } -------------------------------------------------------------------------------- /lib/src/dto/entitlement.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | import 'package:qonversion_flutter/src/dto/entitlement_source.dart'; 3 | import 'package:qonversion_flutter/src/dto/entitlement_renew_state.dart'; 4 | import 'package:qonversion_flutter/src/dto/transaction.dart'; 5 | import 'package:qonversion_flutter/src/internal/mapper.dart'; 6 | 7 | import 'entitlement_grant_type.dart'; 8 | 9 | part 'entitlement.g.dart'; 10 | 11 | @JsonSerializable(createToJson: false) 12 | class QEntitlement { 13 | /// Qonversion Entitlement ID, like premium. 14 | /// 15 | /// See [Create Entitlement](https://qonversion.io/docs/create-entitlement) 16 | @JsonKey(name: 'id') 17 | final String id; 18 | 19 | /// Product ID created in Qonversion Dashboard. 20 | /// 21 | /// See [Create Products](https://qonversion.io/docs/create-products) 22 | @JsonKey(name: 'productId') 23 | final String productId; 24 | 25 | /// A renew state for an associate product that unlocked entitlement 26 | @JsonKey( 27 | name: 'renewState', 28 | unknownEnumValue: QEntitlementRenewState.unknown, 29 | ) 30 | final QEntitlementRenewState renewState; 31 | 32 | /// A source determining where this entitlement is originally from - App Store, Play Store, Stripe, etc. 33 | @JsonKey( 34 | name: 'source', 35 | unknownEnumValue: QEntitlementSource.unknown, 36 | ) 37 | final QEntitlementSource source; 38 | 39 | /// Purchase date 40 | @JsonKey( 41 | name: 'startedTimestamp', 42 | fromJson: QMapper.dateTimeFromNullableSecondsTimestamp, 43 | ) 44 | final DateTime? startedDate; 45 | 46 | /// Expiration date for subscription 47 | @JsonKey( 48 | name: 'expirationTimestamp', 49 | fromJson: QMapper.dateTimeFromNullableSecondsTimestamp, 50 | ) 51 | final DateTime? expirationDate; 52 | 53 | /// Renews count for the entitlement. Renews count starts from the second paid subscription. 54 | /// For example, we have 20 transactions. One is the trial, and one is the first paid transaction after the trial. 55 | /// Renews count is equal to 18. 56 | @JsonKey( 57 | name: 'renewsCount', 58 | defaultValue: 0 59 | ) 60 | final int renewsCount; 61 | 62 | /// Trial start date. 63 | @JsonKey( 64 | name: 'trialStartTimestamp', 65 | fromJson: QMapper.dateTimeFromNullableSecondsTimestamp, 66 | ) 67 | final DateTime? trialStartDate; 68 | 69 | /// First purchase date. 70 | @JsonKey( 71 | name: 'firstPurchaseTimestamp', 72 | fromJson: QMapper.dateTimeFromNullableSecondsTimestamp, 73 | ) 74 | final DateTime? firstPurchaseDate; 75 | 76 | /// Last purchase date. 77 | @JsonKey( 78 | name: 'lastPurchaseTimestamp', 79 | fromJson: QMapper.dateTimeFromNullableSecondsTimestamp, 80 | ) 81 | final DateTime? lastPurchaseDate; 82 | 83 | /// Last activated offer code. 84 | @JsonKey( 85 | name: 'lastActivatedOfferCode' 86 | ) 87 | final String? lastActivatedOfferCode; 88 | 89 | /// Grant type of the entitlement. 90 | @JsonKey( 91 | name: 'grantType', 92 | unknownEnumValue: QEntitlementGrantType.purchase, 93 | fromJson: QMapper.grantTypeFromNullableValue 94 | ) 95 | final QEntitlementGrantType grantType; 96 | 97 | /// Auto-renew disable date. 98 | @JsonKey( 99 | name: 'autoRenewDisableTimestamp', 100 | fromJson: QMapper.dateTimeFromNullableSecondsTimestamp, 101 | ) 102 | final DateTime? autoRenewDisableDate; 103 | 104 | /// Array of the transactions that unlocked current entitlement. 105 | @JsonKey( 106 | name: 'transactions', 107 | fromJson: QMapper.transactionsFromNullableValue 108 | ) 109 | final List transactions; 110 | 111 | /// Use for checking entitlement for current user. 112 | /// Pay attention, isActive == true does not mean that subscription is renewable. 113 | /// Subscription could be canceled, but the user could still have a entitlement 114 | @JsonKey(name: 'active') 115 | final bool isActive; 116 | 117 | const QEntitlement( 118 | this.id, 119 | this.productId, 120 | this.renewState, 121 | this.source, 122 | this.startedDate, 123 | this.expirationDate, 124 | this.isActive, 125 | this.renewsCount, 126 | this.trialStartDate, 127 | this.firstPurchaseDate, 128 | this.lastPurchaseDate, 129 | this.lastActivatedOfferCode, 130 | this.grantType, 131 | this.autoRenewDisableDate, 132 | this.transactions 133 | ); 134 | 135 | factory QEntitlement.fromJson(Map json) => _$QEntitlementFromJson(json); 136 | } 137 | -------------------------------------------------------------------------------- /lib/src/dto/store_product/product_store_details.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | import '../../internal/mapper.dart'; 3 | import '../product_type.dart'; 4 | import 'product_offer_details.dart'; 5 | import 'product_inapp_details.dart'; 6 | 7 | part 'product_store_details.g.dart'; 8 | 9 | /// This class contains all the information about the concrete Google product, 10 | /// either subscription or in-app. In case of a subscription also determines concrete base plan. 11 | @JsonSerializable() 12 | class QProductStoreDetails { 13 | /// Identifier of the base plan to which these details relate. 14 | /// Null for in-app products. 15 | @JsonKey(name: 'basePlanId') 16 | final String? basePlanId; 17 | 18 | /// Identifier of the subscription or the in-app product. 19 | @JsonKey(name: 'productId') 20 | final String productId; 21 | 22 | /// Name of the subscription or the in-app product. 23 | @JsonKey(name: 'name') 24 | final String name; 25 | 26 | /// Title of the subscription or the in-app product. 27 | /// The title includes the name of the app. 28 | @JsonKey(name: 'title') 29 | final String title; 30 | 31 | /// Description of the subscription or the in-app product. 32 | @JsonKey(name: 'description') 33 | final String description; 34 | 35 | /// Offer details for the subscription. 36 | /// Offer details contain all the available variations of purchase offers, 37 | /// including both base plan and eligible base plan + offer combinations 38 | /// from Google Play Console for current [basePlanId]. 39 | /// Null for in-app products. 40 | @JsonKey(name: 'subscriptionOfferDetails', fromJson: QMapper.productOfferDetailsListFromJson) 41 | final List? subscriptionOfferDetails; 42 | 43 | /// The most profitable subscription offer for the client in our opinion from all the available offers. 44 | /// We calculate the cheapest price for the client by comparing all the trial or intro phases 45 | /// and the base plan. 46 | @JsonKey(name: 'defaultSubscriptionOfferDetails', fromJson: QMapper.productOfferDetailsFromJson) 47 | final QProductOfferDetails? defaultSubscriptionOfferDetails; 48 | 49 | /// Subscription offer details containing only the base plan without any offer. 50 | @JsonKey(name: 'basePlanSubscriptionOfferDetails', fromJson: QMapper.productOfferDetailsFromJson) 51 | final QProductOfferDetails? basePlanSubscriptionOfferDetails; 52 | 53 | /// Offer details for the in-app product. 54 | /// Null for subscriptions. 55 | @JsonKey(name: 'inAppOfferDetails', fromJson: QMapper.productInAppDetailsFromJson) 56 | final QProductInAppDetails? inAppOfferDetails; 57 | 58 | /// True, if there is any eligible offer with a trial 59 | /// for this subscription and base plan combination. 60 | /// False otherwise or for an in-app product. 61 | @JsonKey(name: 'hasTrialOffer') 62 | final bool hasTrialOffer; 63 | 64 | /// True, if there is any eligible offer with an intro price 65 | /// for this subscription and base plan combination. 66 | /// False otherwise or for an in-app product. 67 | @JsonKey(name: 'hasIntroOffer') 68 | final bool hasIntroOffer; 69 | 70 | /// True, if there is any eligible offer with a trial or an intro price 71 | /// for this subscription and base plan combination. 72 | /// False otherwise or for an in-app product. 73 | @JsonKey(name: 'hasTrialOrIntroOffer') 74 | final bool hasTrialOrIntroOffer; 75 | 76 | /// The calculated type of the current product. 77 | @JsonKey(name: 'productType', unknownEnumValue: QProductType.unknown) 78 | final QProductType productType; 79 | 80 | /// True, if the product type is InApp. 81 | @JsonKey(name: 'isInApp') 82 | final bool isInApp; 83 | 84 | /// True, if the product type is Subscription. 85 | @JsonKey(name: 'isSubscription') 86 | final bool isSubscription; 87 | 88 | /// True, if the subscription product is prepaid, which means that users pay in advance - 89 | /// they will need to make a new payment to extend their plan. 90 | @JsonKey(name: 'isPrepaid') 91 | final bool isPrepaid; 92 | 93 | /// True, if the subscription product is installment, which means that users commit 94 | /// to pay for a specified amount of periods every month. 95 | @JsonKey(name: 'isInstallment') 96 | final bool isInstallment; 97 | 98 | const QProductStoreDetails( 99 | this.basePlanId, 100 | this.productId, 101 | this.name, 102 | this.title, 103 | this.description, 104 | this.subscriptionOfferDetails, 105 | this.defaultSubscriptionOfferDetails, 106 | this.basePlanSubscriptionOfferDetails, 107 | this.inAppOfferDetails, 108 | this.hasTrialOffer, 109 | this.hasIntroOffer, 110 | this.hasTrialOrIntroOffer, 111 | this.productType, 112 | this.isInApp, 113 | this.isSubscription, 114 | this.isPrepaid, 115 | this.isInstallment, 116 | ); 117 | 118 | factory QProductStoreDetails.fromJson(Map json) => 119 | _$QProductStoreDetailsFromJson(json); 120 | 121 | Map toJson() => _$QProductStoreDetailsToJson(this); 122 | } --------------------------------------------------------------------------------