├── 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 | }
--------------------------------------------------------------------------------