├── .github
└── workflows
│ ├── integration_tests.yml
│ ├── nocodes_release_pull_requests.yml
│ ├── pr-checks.yml
│ ├── prerelease_github.yml
│ ├── publish.yml
│ ├── sdk_minor_prerelease.yml
│ ├── sdk_patch_prerelease.yml
│ ├── sdk_release_pull_requests.yml
│ └── stale_issues.yml
├── .gitignore
├── Gemfile
├── Gemfile.lock
├── README.md
├── build.gradle
├── config
└── detekt
│ ├── baseline.xml
│ └── detekt.yml
├── fastlane
├── Appfile
├── Fastfile
├── README.md
└── report.xml
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── nocodes
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── io
│ │ └── qonversion
│ │ └── nocodes
│ │ ├── NoCodes.kt
│ │ ├── NoCodesConfig.kt
│ │ ├── dto
│ │ ├── LogLevel.kt
│ │ ├── QAction.kt
│ │ ├── QScreenPresentationConfig.kt
│ │ └── QScreenPresentationStyle.kt
│ │ ├── error
│ │ ├── ErrorCode.kt
│ │ ├── NoCodesError.kt
│ │ └── NoCodesException.kt
│ │ ├── interfaces
│ │ ├── NoCodesDelegate.kt
│ │ └── ScreenCustomizationDelegate.kt
│ │ └── internal
│ │ ├── NoCodesInternal.kt
│ │ ├── common
│ │ ├── BaseClass.kt
│ │ ├── StorageConstants.kt
│ │ ├── internalConstants.kt
│ │ ├── localStorage
│ │ │ ├── LocalStorage.kt
│ │ │ └── SharedPreferencesStorage.kt
│ │ ├── mappers
│ │ │ ├── ActionMapper.kt
│ │ │ ├── Mapper.kt
│ │ │ ├── ScreenMapper.kt
│ │ │ ├── error
│ │ │ │ ├── ApiErrorMapper.kt
│ │ │ │ └── ErrorResponseMapper.kt
│ │ │ └── extensions.kt
│ │ └── serializers
│ │ │ ├── JsonSerializer.kt
│ │ │ └── Serializer.kt
│ │ ├── di
│ │ ├── DependenciesAssembly.kt
│ │ ├── controllers
│ │ │ ├── ControllersAssembly.kt
│ │ │ └── ControllersAssemblyImpl.kt
│ │ ├── mappers
│ │ │ ├── MappersAssembly.kt
│ │ │ └── MappersAssemblyImpl.kt
│ │ ├── misc
│ │ │ ├── MiscAssembly.kt
│ │ │ └── MiscAssemblyImpl.kt
│ │ ├── network
│ │ │ ├── NetworkAssembly.kt
│ │ │ └── NetworkAssemblyImpl.kt
│ │ ├── services
│ │ │ ├── ServicesAssembly.kt
│ │ │ └── ServicesAssemblyImpl.kt
│ │ └── storage
│ │ │ ├── StorageAssembly.kt
│ │ │ └── StorageAssemblyImpl.kt
│ │ ├── dto
│ │ ├── NoCodeScreen.kt
│ │ └── config
│ │ │ ├── InternalConfig.kt
│ │ │ ├── LoggerConfig.kt
│ │ │ ├── NetworkConfig.kt
│ │ │ └── PrimaryConfig.kt
│ │ ├── logger
│ │ ├── ConsoleLogger.kt
│ │ └── Logger.kt
│ │ ├── networkLayer
│ │ ├── ApiHeader.kt
│ │ ├── RetryPolicy.kt
│ │ ├── apiInteractor
│ │ │ ├── ApiInteractor.kt
│ │ │ └── ApiInteractorImpl.kt
│ │ ├── dto
│ │ │ ├── RawResponse.kt
│ │ │ ├── Request.kt
│ │ │ └── Response.kt
│ │ ├── headerBuilder
│ │ │ ├── HeaderBuilder.kt
│ │ │ └── HeaderBuilderImpl.kt
│ │ ├── networkClient
│ │ │ ├── NetworkClient.kt
│ │ │ └── NetworkClientImpl.kt
│ │ ├── requestConfigurator
│ │ │ ├── ApiEndpoint.kt
│ │ │ ├── RequestConfigurator.kt
│ │ │ └── RequestConfiguratorImpl.kt
│ │ ├── retryDelayCalculator
│ │ │ ├── ExponentialDelayCalculator.kt
│ │ │ └── RetryDelayCalculator.kt
│ │ └── utils
│ │ │ └── extensions.kt
│ │ ├── provider
│ │ ├── LoggerConfigProvider.kt
│ │ ├── NetworkConfigHolder.kt
│ │ ├── NoCodesDelegateProvider.kt
│ │ └── PrimaryConfigProvider.kt
│ │ ├── screen
│ │ ├── controller
│ │ │ ├── ScreenController.kt
│ │ │ └── ScreenControllerImpl.kt
│ │ ├── misc
│ │ │ └── ActivityProvider.kt
│ │ ├── service
│ │ │ ├── ScreenService.kt
│ │ │ └── ScreenServiceImpl.kt
│ │ ├── utils.kt
│ │ └── view
│ │ │ ├── ScreenActivity.kt
│ │ │ ├── ScreenContract.kt
│ │ │ ├── ScreenFragment.kt
│ │ │ └── ScreenPresenter.kt
│ │ └── utils
│ │ └── dateUtils.kt
│ └── res
│ ├── anim
│ ├── nc_fade_in.xml
│ ├── nc_fade_out.xml
│ ├── nc_slide_in_from_bottom.xml
│ ├── nc_slide_in_from_left.xml
│ ├── nc_slide_out_to_bottom.xml
│ └── nc_slide_out_to_left.xml
│ ├── layout
│ ├── nc_activity_screen.xml
│ ├── nc_fragment_screen.xml
│ └── nc_progress_bar.xml
│ └── values
│ ├── colors.xml
│ └── styles.xml
├── sample
├── .gitignore
├── build.gradle
├── google-services.json
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── ic_launcher-playstore.png
│ ├── java
│ └── io
│ │ └── qonversion
│ │ └── sample
│ │ ├── App.java
│ │ ├── AuthActivity.kt
│ │ ├── EntitlementsAdapter.kt
│ │ ├── EntitlementsFragment.kt
│ │ ├── HomeFragment.kt
│ │ ├── MainActivity.kt
│ │ ├── OfferingsFragment.kt
│ │ ├── ProductsAdapter.kt
│ │ └── utils.kt
│ └── res
│ ├── drawable-v24
│ └── ic_launcher_foreground.xml
│ ├── drawable
│ ├── ic_entitlement.xml
│ ├── ic_launcher_background.xml
│ ├── ic_notification.png
│ ├── ic_offering.xml
│ └── ic_qonversion.xml
│ ├── layout
│ ├── activity_auth.xml
│ ├── activity_main.xml
│ ├── dialog_configuration.xml
│ ├── fragment_entitlements.xml
│ ├── fragment_home.xml
│ ├── fragment_offerings.xml
│ ├── table_row_entitlement.xml
│ └── table_row_product.xml
│ ├── menu
│ └── drawer_bottom_nav_menu.xml
│ ├── mipmap-anydpi-v26
│ ├── ic_launcher.xml
│ └── ic_launcher_round.xml
│ ├── mipmap-hdpi
│ ├── ic_launcher.webp
│ ├── ic_launcher_foreground.webp
│ └── ic_launcher_round.webp
│ ├── mipmap-mdpi
│ ├── ic_launcher.webp
│ ├── ic_launcher_foreground.webp
│ └── ic_launcher_round.webp
│ ├── mipmap-xhdpi
│ ├── ic_launcher.webp
│ ├── ic_launcher_foreground.webp
│ └── ic_launcher_round.webp
│ ├── mipmap-xxhdpi
│ ├── ic_launcher.webp
│ ├── ic_launcher_foreground.webp
│ └── ic_launcher_round.webp
│ ├── mipmap-xxxhdpi
│ ├── ic_launcher.webp
│ ├── ic_launcher_foreground.webp
│ └── ic_launcher_round.webp
│ ├── navigation
│ └── nav_graph.xml
│ └── values
│ ├── colors.xml
│ ├── dimens.xml
│ ├── strings.xml
│ └── styles.xml
├── scripts
└── maven-release.gradle
├── sdk
├── .gitignore
├── build.gradle
├── consumer-rules.pro
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── qonversion
│ │ └── android
│ │ └── sdk
│ │ └── internal
│ │ ├── OutagerIntegrationTest.kt
│ │ └── QonversionRepositoryIntegrationTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── qonversion
│ │ │ └── android
│ │ │ └── sdk
│ │ │ ├── Qonversion.kt
│ │ │ ├── QonversionConfig.kt
│ │ │ ├── automations
│ │ │ ├── Automations.kt
│ │ │ ├── AutomationsDelegate.java
│ │ │ ├── ScreenCustomizationDelegate.kt
│ │ │ ├── dto
│ │ │ │ ├── AutomationsEvent.kt
│ │ │ │ ├── AutomationsEventType.kt
│ │ │ │ ├── QActionResult.kt
│ │ │ │ ├── QActionResultType.kt
│ │ │ │ ├── QScreenPresentationConfig.kt
│ │ │ │ └── QScreenPresentationStyle.kt
│ │ │ ├── internal
│ │ │ │ ├── ActivityProvider.kt
│ │ │ │ ├── AutomationsEventMapper.kt
│ │ │ │ ├── AutomationsInternal.kt
│ │ │ │ ├── QAutomationsManager.kt
│ │ │ │ ├── macros
│ │ │ │ │ ├── Macros.kt
│ │ │ │ │ ├── MacrosType.kt
│ │ │ │ │ └── ScreenProcessor.kt
│ │ │ │ └── utils.kt
│ │ │ └── mvp
│ │ │ │ ├── ScreenActivity.kt
│ │ │ │ ├── ScreenContract.kt
│ │ │ │ ├── ScreenFragment.kt
│ │ │ │ └── ScreenPresenter.kt
│ │ │ ├── dto
│ │ │ ├── QAttributionProvider.kt
│ │ │ ├── QEnvironment.kt
│ │ │ ├── QFallbackObject.kt
│ │ │ ├── QLaunchMode.kt
│ │ │ ├── QPurchaseModel.kt
│ │ │ ├── QPurchaseOptions.kt
│ │ │ ├── QPurchaseUpdateModel.kt
│ │ │ ├── QPurchaseUpdatePolicy.kt
│ │ │ ├── QRemoteConfig.kt
│ │ │ ├── QRemoteConfigList.kt
│ │ │ ├── QRemoteConfigurationAssignmentType.kt
│ │ │ ├── QRemoteConfigurationSource.kt
│ │ │ ├── QRemoteConfigurationSourceType.kt
│ │ │ ├── QUser.kt
│ │ │ ├── QonversionError.kt
│ │ │ ├── eligibility
│ │ │ │ ├── QEligibility.kt
│ │ │ │ └── QIntroEligibilityStatus.kt
│ │ │ ├── entitlements
│ │ │ │ ├── QEntitlement.kt
│ │ │ │ ├── QEntitlementGrantType.kt
│ │ │ │ ├── QEntitlementRenewState.kt
│ │ │ │ ├── QEntitlementSource.kt
│ │ │ │ ├── QEntitlementsCacheLifetime.kt
│ │ │ │ ├── QTransaction.kt
│ │ │ │ ├── QTransactionEnvironment.kt
│ │ │ │ ├── QTransactionOwnershipType.kt
│ │ │ │ └── QTransactionType.kt
│ │ │ ├── experiments
│ │ │ │ ├── QExperiment.kt
│ │ │ │ ├── QExperimentGroup.kt
│ │ │ │ └── QExperimentGroupType.kt
│ │ │ ├── offerings
│ │ │ │ ├── QOffering.kt
│ │ │ │ ├── QOfferingTag.kt
│ │ │ │ └── QOfferings.kt
│ │ │ ├── products
│ │ │ │ ├── QProduct.kt
│ │ │ │ ├── QProductInAppDetails.kt
│ │ │ │ ├── QProductInstallmentPlanDetails.kt
│ │ │ │ ├── QProductOfferDetails.kt
│ │ │ │ ├── QProductPrice.kt
│ │ │ │ ├── QProductPricingPhase.kt
│ │ │ │ ├── QProductStoreDetails.kt
│ │ │ │ ├── QProductType.kt
│ │ │ │ └── QSubscriptionPeriod.kt
│ │ │ └── properties
│ │ │ │ ├── QUserProperties.kt
│ │ │ │ ├── QUserProperty.kt
│ │ │ │ └── QUserPropertyKey.kt
│ │ │ ├── internal
│ │ │ ├── AdvertisingProvider.kt
│ │ │ ├── AppLifeCycleHandler.kt
│ │ │ ├── AppState.kt
│ │ │ ├── Constants.kt
│ │ │ ├── EnvironmentProvider.kt
│ │ │ ├── FacebookAttribution.kt
│ │ │ ├── HttpError.kt
│ │ │ ├── IncrementalDelayCalculator.kt
│ │ │ ├── InternalConfig.kt
│ │ │ ├── LoadStoreProductsState.kt
│ │ │ ├── QAttributionManager.kt
│ │ │ ├── QHandledPurchasesCache.kt
│ │ │ ├── QIdentityManager.kt
│ │ │ ├── QProductCenterManager.kt
│ │ │ ├── QRemoteConfigManager.kt
│ │ │ ├── QUserPropertiesManager.kt
│ │ │ ├── QonversionFactory.kt
│ │ │ ├── QonversionInternal.kt
│ │ │ ├── api
│ │ │ │ ├── Api.kt
│ │ │ │ ├── ApiErrorMapper.kt
│ │ │ │ ├── ApiHeadersProvider.kt
│ │ │ │ ├── ApiHelper.kt
│ │ │ │ ├── NetworkInterceptor.kt
│ │ │ │ ├── RateLimiter.kt
│ │ │ │ ├── RequestTrigger.kt
│ │ │ │ └── RequestType.kt
│ │ │ ├── billing
│ │ │ │ ├── BillingClientHolder.kt
│ │ │ │ ├── BillingClientWrapper.kt
│ │ │ │ ├── BillingClientWrapperBase.kt
│ │ │ │ ├── BillingError.kt
│ │ │ │ ├── BillingService.kt
│ │ │ │ ├── IBillingClientWrapper.kt
│ │ │ │ ├── LegacyBillingClientWrapper.kt
│ │ │ │ ├── QonversionBillingService.kt
│ │ │ │ ├── UpdatePurchaseInfo.kt
│ │ │ │ └── utils.kt
│ │ │ ├── converter
│ │ │ │ ├── GooglePurchaseConverter.kt
│ │ │ │ └── PurchaseConverter.kt
│ │ │ ├── di
│ │ │ │ ├── QDependencyInjector.kt
│ │ │ │ ├── component
│ │ │ │ │ ├── AppComponent.kt
│ │ │ │ │ └── FragmentComponent.kt
│ │ │ │ ├── module
│ │ │ │ │ ├── AppModule.kt
│ │ │ │ │ ├── FragmentModule.kt
│ │ │ │ │ ├── ManagersModule.kt
│ │ │ │ │ ├── NetworkModule.kt
│ │ │ │ │ ├── RepositoryModule.kt
│ │ │ │ │ └── ServicesModule.kt
│ │ │ │ └── scope
│ │ │ │ │ ├── ActivityScope.kt
│ │ │ │ │ └── ApplicationScope.kt
│ │ │ ├── dto
│ │ │ │ ├── ActionPoints.kt
│ │ │ │ ├── BaseResponse.kt
│ │ │ │ ├── Data.kt
│ │ │ │ ├── Environment.kt
│ │ │ │ ├── ProductStoreId.kt
│ │ │ │ ├── ProviderData.kt
│ │ │ │ ├── QLaunchResult.kt
│ │ │ │ ├── QPermission.kt
│ │ │ │ ├── QProductRenewState.kt
│ │ │ │ ├── QStoreProductType.kt
│ │ │ │ ├── QonversionMappingAdapters.kt
│ │ │ │ ├── Response.kt
│ │ │ │ ├── SendPropertiesResult.kt
│ │ │ │ ├── app
│ │ │ │ │ └── App.kt
│ │ │ │ ├── automations
│ │ │ │ │ ├── ActionPointScreen.kt
│ │ │ │ │ └── Screen.kt
│ │ │ │ ├── config
│ │ │ │ │ ├── CacheConfig.kt
│ │ │ │ │ └── PrimaryConfig.kt
│ │ │ │ ├── device
│ │ │ │ │ └── Os.kt
│ │ │ │ ├── eligibility
│ │ │ │ │ ├── EligibilityResult.kt
│ │ │ │ │ ├── ProductEligibility.kt
│ │ │ │ │ └── StoreProductInfo.kt
│ │ │ │ ├── identity
│ │ │ │ │ └── IdentityResult.kt
│ │ │ │ ├── purchase
│ │ │ │ │ ├── History.kt
│ │ │ │ │ ├── Inapp.kt
│ │ │ │ │ ├── PurchaseModelInternal.kt
│ │ │ │ │ └── PurchaseModelInternalEnriched.kt
│ │ │ │ └── request
│ │ │ │ │ ├── AttachUserRequest.kt
│ │ │ │ │ ├── AttributionRequest.kt
│ │ │ │ │ ├── CrashRequest.kt
│ │ │ │ │ ├── EligibilityRequest.kt
│ │ │ │ │ ├── IdentityRequest.kt
│ │ │ │ │ ├── InitRequest.kt
│ │ │ │ │ ├── PurchaseRequest.kt
│ │ │ │ │ ├── RequestData.kt
│ │ │ │ │ ├── RestoreRequest.kt
│ │ │ │ │ ├── SendPushTokenRequest.kt
│ │ │ │ │ ├── ViewsRequest.kt
│ │ │ │ │ └── data
│ │ │ │ │ ├── InitRequestData.kt
│ │ │ │ │ └── UserPropertyRequestData.kt
│ │ │ ├── errors.kt
│ │ │ ├── extensions.kt
│ │ │ ├── logger
│ │ │ │ ├── ConsoleLogger.kt
│ │ │ │ ├── ExceptionHandler.kt
│ │ │ │ ├── ExceptionManager.kt
│ │ │ │ ├── Logger.kt
│ │ │ │ └── QExceptionManager.kt
│ │ │ ├── provider
│ │ │ │ ├── AppStateProvider.kt
│ │ │ │ ├── CacheConfigProvider.kt
│ │ │ │ ├── EntitlementsUpdateListenerProvider.kt
│ │ │ │ ├── EnvironmentProvider.kt
│ │ │ │ ├── PrimaryConfigProvider.kt
│ │ │ │ ├── UidProvider.kt
│ │ │ │ └── UserStateProvider.kt
│ │ │ ├── purchase
│ │ │ │ ├── Purchase.kt
│ │ │ │ └── PurchaseHistory.kt
│ │ │ ├── repository
│ │ │ │ ├── DefaultRepository.kt
│ │ │ │ ├── QRepository.kt
│ │ │ │ └── RepositoryWithRateLimits.kt
│ │ │ ├── services
│ │ │ │ ├── QFallbacksService.kt
│ │ │ │ ├── QRemoteConfigService.kt
│ │ │ │ └── QUserInfoService.kt
│ │ │ ├── storage
│ │ │ │ ├── Cache.kt
│ │ │ │ ├── LaunchResultCacheWrapper.kt
│ │ │ │ ├── PropertiesStorage.kt
│ │ │ │ ├── PurchasesCache.kt
│ │ │ │ ├── SharedPreferencesCache.kt
│ │ │ │ ├── Storage.kt
│ │ │ │ ├── TokenStorage.kt
│ │ │ │ └── UserPropertiesStorage.kt
│ │ │ └── utils.kt
│ │ │ └── listeners
│ │ │ ├── QEntitlementsUpdateListener.kt
│ │ │ └── QonversionCallback.kt
│ └── res
│ │ ├── anim
│ │ ├── q_fade_in.xml
│ │ ├── q_fade_out.xml
│ │ ├── q_slide_in_from_bottom.xml
│ │ ├── q_slide_in_from_left.xml
│ │ ├── q_slide_out_to_bottom.xml
│ │ └── q_slide_out_to_left.xml
│ │ ├── layout
│ │ ├── q_activity_screen.xml
│ │ ├── q_fragment_screen.xml
│ │ └── q_progress_bar.xml
│ │ └── values
│ │ ├── colors.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── com
│ └── qonversion
│ └── android
│ └── sdk
│ ├── QonversionConfigTest.kt
│ ├── automations
│ ├── internal
│ │ ├── AutomationsEventMapperTest.kt
│ │ └── QAutomationsManagerTest.kt
│ └── mvp
│ │ └── ScreenPresenterTest.kt
│ ├── internal
│ ├── IncrementalDelayCalculatorTest.kt
│ ├── InternalConfigTest.kt
│ ├── QAttributionManagerTest.kt
│ ├── QHandledPurchasesCacheTest.kt
│ ├── QIdentityManagerTest.kt
│ ├── QProductCenterManagerTest.kt
│ ├── QUserPropertiesManagerTest.kt
│ ├── api
│ │ └── ApiHelperTest.kt
│ ├── requests
│ │ ├── AppRequestTest.kt
│ │ ├── OsRequestTest.kt
│ │ └── ProviderDataRequestTest.kt
│ ├── services
│ │ └── QUserInfoServiceTest.kt
│ └── storage
│ │ ├── LaunchResultCacheWrapperTest.kt
│ │ ├── PurchasesCacheTest.kt
│ │ ├── SharedPreferencesCacheTest.kt
│ │ ├── UserPropertiesStorageTest.kt
│ │ └── util.kt
│ └── utils.kt
└── settings.gradle
/.github/workflows/integration_tests.yml:
--------------------------------------------------------------------------------
1 | name: Planned integration tests
2 |
3 | on:
4 | workflow_dispatch:
5 | schedule:
6 | - cron: '0 3 * * *'
7 |
8 | jobs:
9 | testing:
10 | runs-on: ubuntu-latest
11 |
12 | steps:
13 | - uses: actions/checkout@v2
14 |
15 | - name: set up JDK 18
16 | uses: actions/setup-java@v1
17 | with:
18 | java-version: 18
19 |
20 | - name: Set Outager url
21 | run: |
22 | fastlane setOutagerUrl url:${{ secrets.OUTAGER_URL }}
23 |
24 | - name: Build and Tests
25 | uses: reactivecircus/android-emulator-runner@v2
26 | with:
27 | api-level: 28
28 | script: ./gradlew sdk:connectedAndroidTest
29 |
30 | - name: Unit tests results
31 | if: always()
32 | uses: actions/upload-artifact@v4
33 | with:
34 | name: test-results
35 | path: sdk/build/reports/androidTests/connected/**
--------------------------------------------------------------------------------
/.github/workflows/nocodes_release_pull_requests.yml:
--------------------------------------------------------------------------------
1 | name: Release pull requests
2 | on:
3 | push:
4 | tags:
5 | - prerelease-nocodes/*
6 |
7 | jobs:
8 | prerelease:
9 | runs-on: macos-latest
10 |
11 | steps:
12 | - uses: actions/checkout@v2
13 | name: Checkout all
14 | with:
15 | fetch-depth: 0
16 |
17 | - uses: olegtarasov/get-tag@v2.1
18 | id: tagName
19 | with:
20 | tagRegex: 'prerelease-nocodes\/(\d*\.\d*\.\d*)'
21 |
22 | - name: Bump version
23 | run: |
24 | fastlane bump lib:nocodes version:${{ steps.tagName.outputs.tag }}
25 |
26 | - name: Create pull request
27 | uses: peter-evans/create-pull-request@v3
28 | with:
29 | title: No-Codes Release ${{ steps.tagName.outputs.tag }}
30 | body: No-Codes Release Pull Request
31 | labels: autocreated
32 | branch: release-nocodes/${{ steps.tagName.outputs.tag }}
33 | base: develop
34 |
35 | - uses: actions/checkout@v2
36 | with:
37 | ref: main
38 |
39 | - name: Reset main branch
40 | run: |
41 | git fetch origin release-nocodes/${{ steps.tagName.outputs.tag }}:release-nocodes/${{ steps.tagName.outputs.tag }}
42 | git reset --hard release-nocodes/${{ steps.tagName.outputs.tag }}
43 |
44 | - name: Create Pull Request
45 | uses: peter-evans/create-pull-request@v3
46 | with:
47 | title: No-Codes Release ${{ steps.tagName.outputs.tag }}
48 | body: No-Codes Release Pull Request
49 | labels: autocreated
50 | branch: release-nocodes/${{ steps.tagName.outputs.tag }}
51 | base: main
52 |
--------------------------------------------------------------------------------
/.github/workflows/pr-checks.yml:
--------------------------------------------------------------------------------
1 | name: Tests
2 |
3 | on:
4 | pull_request:
5 | types: [opened, synchronize, reopened]
6 |
7 | jobs:
8 | build:
9 |
10 | runs-on: ubuntu-latest
11 |
12 | steps:
13 | - uses: actions/checkout@v2
14 |
15 | - name: set up JDK 18
16 | uses: actions/setup-java@v1
17 | with:
18 | java-version: 18
19 |
20 | - name: Make gradlew executable
21 | run: chmod +x ./gradlew
22 |
23 | - name: Detekt
24 | run: ./gradlew detektAll
25 |
26 | - name: Set up Ruby 2.6
27 | uses: ruby/setup-ruby@v1
28 | with:
29 | ruby-version: 2.6.0
30 |
31 | - name: Install dependencies
32 | run: bundle install
33 |
34 | - name: Fastlane Action
35 | uses: maierj/fastlane-action@v1.4.0
36 | with:
37 | lane: test
38 | - name: Build and Tests
39 | run: |
40 | fastlane test
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/.github/workflows/sdk_minor_prerelease.yml:
--------------------------------------------------------------------------------
1 | name: Manual Minor Prerelease
2 |
3 | on:
4 | workflow_dispatch
5 |
6 | jobs:
7 | minor-prerelease:
8 | runs-on: ubuntu-latest
9 |
10 | steps:
11 | - uses: actions/checkout@v2
12 | with:
13 | ref: develop
14 | fetch-depth: 0
15 |
16 | - name: Minor
17 | run: |
18 | fastlane minor lib:sdk
19 |
--------------------------------------------------------------------------------
/.github/workflows/sdk_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 | fetch-depth: 0
15 |
16 | - name: Patch
17 | run: |
18 | fastlane patch lib:sdk
19 |
--------------------------------------------------------------------------------
/.github/workflows/sdk_release_pull_requests.yml:
--------------------------------------------------------------------------------
1 | name: Release pull requests
2 | on:
3 | push:
4 | tags:
5 | - prerelease-sdk/*
6 |
7 | jobs:
8 | prerelease:
9 | runs-on: macos-latest
10 |
11 | steps:
12 | - uses: actions/checkout@v2
13 | name: Checkout all
14 | with:
15 | fetch-depth: 0
16 |
17 | - uses: olegtarasov/get-tag@v2.1
18 | id: tagName
19 | with:
20 | tagRegex: 'prerelease-sdk\/(\d*\.\d*\.\d*)'
21 |
22 | - name: Bump version
23 | run: |
24 | fastlane bump lib:sdk version:${{ steps.tagName.outputs.tag }}
25 |
26 | - name: Create pull request
27 | uses: peter-evans/create-pull-request@v3
28 | with:
29 | title: Qonversion Release ${{ steps.tagName.outputs.tag }}
30 | body: Qonversion Release Pull Request
31 | labels: autocreated
32 | branch: release-sdk/${{ steps.tagName.outputs.tag }}
33 | base: develop
34 |
35 | - uses: actions/checkout@v2
36 | with:
37 | ref: main
38 |
39 | - name: Reset main branch
40 | run: |
41 | git fetch origin release-sdk/${{ steps.tagName.outputs.tag }}:release-sdk/${{ steps.tagName.outputs.tag }}
42 | git reset --hard release-sdk/${{ steps.tagName.outputs.tag }}
43 |
44 | - name: Create Pull Request
45 | uses: peter-evans/create-pull-request@v3
46 | with:
47 | title: Qonversion Release ${{ steps.tagName.outputs.tag }}
48 | body: Qonversion Release Pull Request
49 | labels: autocreated
50 | branch: release-sdk/${{ steps.tagName.outputs.tag }}
51 | base: main
52 |
--------------------------------------------------------------------------------
/.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
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | gem "fastlane"
4 |
--------------------------------------------------------------------------------
/fastlane/Appfile:
--------------------------------------------------------------------------------
1 | json_key_file("") # Path to the json secret file - Follow https://docs.fastlane.tools/actions/supply/#setup to get one
2 | package_name("com.qonversion.android.sdk") # e.g. com.krausefx.app
3 |
--------------------------------------------------------------------------------
/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 | ## Android
17 |
18 | ### android test
19 |
20 | ```sh
21 | [bundle exec] fastlane android test
22 | ```
23 |
24 | Runs all the tests
25 |
26 | ### android patch
27 |
28 | ```sh
29 | [bundle exec] fastlane android patch
30 | ```
31 |
32 |
33 |
34 | ### android minor
35 |
36 | ```sh
37 | [bundle exec] fastlane android minor
38 | ```
39 |
40 |
41 |
42 | ### android bump
43 |
44 | ```sh
45 | [bundle exec] fastlane android bump
46 | ```
47 |
48 |
49 |
50 | ### android setOutagerUrl
51 |
52 | ```sh
53 | [bundle exec] fastlane android setOutagerUrl
54 | ```
55 |
56 |
57 |
58 | ----
59 |
60 | This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run.
61 |
62 | More information about _fastlane_ can be found on [fastlane.tools](https://fastlane.tools).
63 |
64 | The documentation of _fastlane_ can be found on [docs.fastlane.tools](https://docs.fastlane.tools).
65 |
--------------------------------------------------------------------------------
/fastlane/report.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536m
2 | # When configured, Gradle will run in incubating parallel mode.
3 | # This option should only be used with decoupled projects. More details, visit
4 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
5 | # org.gradle.parallel=true
6 | # AndroidX package structure to make it clearer which packages are bundled with the
7 | # Android operating system, and which are packaged with your app's APK
8 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
9 | android.useAndroidX=true
10 | # Automatically convert third-party libraries to use AndroidX
11 | android.enableJetifier=true
12 | android.enableD8.libraries=false
13 | kotlin.code.style=official
14 |
15 | # Maven Central release
16 | POM_NAME=qonversion-android
17 | POM_PACKAGING=aar
18 |
19 | POM_DESCRIPTION=The subscription data platform for mobile-first companies.
20 |
21 | POM_URL=https://github.com/qonversion/android-sdk
22 | POM_SCM_URL=https://github.com/qonversion/android-sdk
23 | POM_SCM_CONNECTION=scm:git:git://github.com/qonversion/android-sdk.git
24 | POM_SCM_DEV_CONNECTION=scm:git:ssh://git@github.com/qonversion/android-sdk.git
25 |
26 | POM_LICENCE_NAME=The MIT License (MIT)
27 | POM_LICENCE_URL=http://opensource.org/licenses/MIT
28 | POM_LICENCE_DIST=repo
29 |
30 | POM_DEVELOPER_ID=qonversion
31 | POM_DEVELOPER_NAME=Qonversion
32 |
33 | SONATYPE_HOST=CENTRAL_PORTAL
34 | RELEASE_SIGNING_ENABLED=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qonversion/android-sdk/15d67725d1139ba7217e19f5cee00b4a1867a809/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sat Nov 30 17:26:04 MSK 2019
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip
--------------------------------------------------------------------------------
/nocodes/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/nocodes/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.library'
3 | id 'org.jetbrains.kotlin.android'
4 | }
5 |
6 | android {
7 | namespace 'io.qonversion.nocodes'
8 | compileSdk 34
9 |
10 | defaultConfig {
11 | minSdk 21
12 | targetSdk 34
13 |
14 | group = 'io.qonversion.nocodes'
15 | version = nocodes.versionName
16 | versionName = nocodes.versionName
17 | namespace = 'io.qonversion.nocodes'
18 |
19 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
20 | }
21 |
22 | buildTypes {
23 | buildTypes.each {
24 | it.buildConfigField("String","VERSION_NAME", "\"${defaultConfig.versionName}\"")
25 | }
26 | release {
27 | minifyEnabled false
28 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
29 | }
30 | }
31 |
32 | kotlin {
33 | jvmToolchain(17)
34 | }
35 |
36 | buildFeatures {
37 | viewBinding true
38 | buildConfig true
39 | }
40 | }
41 |
42 | ext {
43 | PUBLISH_GROUP_ID = 'io.qonversion'
44 | PUBLISH_ARTIFACT_ID = 'no-codes'
45 | }
46 |
47 | dependencies {
48 |
49 | implementation 'androidx.core:core-ktx:1.13.1'
50 | implementation 'androidx.appcompat:appcompat:1.6.1'
51 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1"
52 | api project(':sdk')
53 | }
54 |
55 | apply from: "../scripts/maven-release.gradle"
--------------------------------------------------------------------------------
/nocodes/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/nocodes/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/nocodes/src/main/java/io/qonversion/nocodes/dto/LogLevel.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.nocodes.dto
2 |
3 | import io.qonversion.nocodes.NoCodes
4 |
5 | /**
6 | * This enum contains available settings for LogLevel.
7 | * Provide it to the configuration object via [NoCodes.initialize] while initializing the SDK
8 | * or via [NoCodes.setLogLevel] after initializing the SDK.
9 | * The default value SDK uses is [LogLevel.Info].
10 | *
11 | * You could change the log level to make logs from the Qonversion No-Codes SDK more detailed or strict.
12 | */
13 | enum class LogLevel(val level: Int) {
14 | // All available logs (function started, function finished, data fetched, etc)
15 | Verbose(0),
16 |
17 | // Info level (data fetched, products loaded, screen fetched, etc)
18 | Info(10),
19 |
20 | // Warning level (data fetched partially, etc)
21 | Warning(20),
22 |
23 | // Error level (data fetch failed, activity failed to start, etc)
24 | Error(30),
25 |
26 | // Logging is disabled at all
27 | Disabled(Int.MAX_VALUE)
28 | }
29 |
--------------------------------------------------------------------------------
/nocodes/src/main/java/io/qonversion/nocodes/dto/QAction.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.nocodes.dto
2 |
3 | import io.qonversion.nocodes.error.NoCodesError
4 |
5 | data class QAction(
6 | val type: Type,
7 | val parameters: Map? = null
8 | ) {
9 | var error: NoCodesError? = null
10 |
11 | constructor(type: Type, parameter: Parameter, value: Any) : this(type, mapOf(parameter to value))
12 |
13 | enum class Type(val type: String) {
14 | Unknown("unknown"),
15 | Url("url"),
16 | DeepLink("deeplink"),
17 | Navigation("navigation"),
18 | Purchase("makePurchase"),
19 | Restore("restore"),
20 | Close("close"),
21 | CloseAll("closeAll"),
22 | LoadProducts("getProducts"),
23 | ShowScreen("showScreen");
24 |
25 | companion object {
26 | fun from(type: String?) = entries.find { it.type == type } ?: Unknown
27 | }
28 | }
29 |
30 | enum class Parameter(val key: String) {
31 | Url("url"),
32 | Deeplink("deeplink"),
33 | ProductId("productId"),
34 | ProductIds("productIds"),
35 | ScreenId("screenId");
36 |
37 | companion object {
38 | fun from(key: String?) = entries.find { it.key == key }
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/nocodes/src/main/java/io/qonversion/nocodes/dto/QScreenPresentationConfig.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.nocodes.dto
2 |
3 | data class QScreenPresentationConfig(
4 | val presentationStyle: QScreenPresentationStyle = QScreenPresentationStyle.Push
5 | )
6 |
--------------------------------------------------------------------------------
/nocodes/src/main/java/io/qonversion/nocodes/dto/QScreenPresentationStyle.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.nocodes.dto
2 |
3 | enum class QScreenPresentationStyle {
4 | Push, /** default screen transaction animation will be used */
5 | FullScreen, /** screen will move from bottom to top */
6 | NoAnimation, /** screen will appear/disappear without any animation */
7 | }
8 |
--------------------------------------------------------------------------------
/nocodes/src/main/java/io/qonversion/nocodes/error/ErrorCode.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.nocodes.error
2 |
3 | /**
4 | * This enum contains all available values of ErrorCode that QonversionException may contain.
5 | */
6 | enum class ErrorCode(val defaultMessage: String) {
7 | ActivityStart("Failed to start activity for No-Code screen"),
8 | BadNetworkRequest("Network request is incorrect and thus cannot be executed"),
9 | NetworkRequestExecution("Failed to execute network request"),
10 | Serialization("Failed to serialize data"),
11 | Deserialization("Failed to deserialize data"),
12 | RequestDenied("Request denied"),
13 | Mapping("Failed to map response"),
14 | BadResponse("API response can not be handled"),
15 | BackendError("Qonversion API returned an error"),
16 | ScreenNotFound("No-Code screen not found"),
17 | QonversionError("An internal error from Qonversion SDK. For more details look at the nested error."),
18 | }
19 |
--------------------------------------------------------------------------------
/nocodes/src/main/java/io/qonversion/nocodes/error/NoCodesError.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.nocodes.error
2 |
3 | import com.qonversion.android.sdk.dto.QonversionError
4 |
5 | class NoCodesError(
6 | val code: ErrorCode,
7 | val details: String? = null,
8 | val qonversionError: QonversionError? = null, /** is present, if the [code] is [ErrorCode.QonversionError] */
9 | ) {
10 | constructor(qonversionError: QonversionError) : this(ErrorCode.QonversionError, null, qonversionError)
11 |
12 | constructor(noCodesException: NoCodesException) : this(noCodesException.code, noCodesException.message)
13 |
14 | override fun toString(): String {
15 | return "NoCodesError: {code=$code, description=${code.defaultMessage}, details=${details ?: ""}"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/nocodes/src/main/java/io/qonversion/nocodes/error/NoCodesException.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.nocodes.error
2 |
3 | class NoCodesException(
4 | val code: ErrorCode,
5 | details: String? = null,
6 | cause: Throwable? = null
7 | ) : Exception(details, cause) {
8 |
9 | constructor(error: NoCodesError) : this(error.code, error.details)
10 |
11 | /**
12 | * Call this function to get prettified exception info
13 | */
14 | override fun toString(): String {
15 | val builder = StringBuilder()
16 | builder.append("Qonversion NoCodesException: ${code.defaultMessage}.")
17 | message?.let {
18 | builder.append(" $message.")
19 | }
20 | return builder.toString()
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/nocodes/src/main/java/io/qonversion/nocodes/interfaces/NoCodesDelegate.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.nocodes.interfaces
2 |
3 | import io.qonversion.nocodes.dto.QAction
4 | import io.qonversion.nocodes.NoCodes
5 | import io.qonversion.nocodes.error.NoCodesError
6 | import com.qonversion.android.sdk.Qonversion
7 |
8 | interface NoCodesDelegate {
9 |
10 | /**
11 | * Called when No-Code screen is shown.
12 | *
13 | * @param screenId shown screen Id.
14 | */
15 | fun onScreenShown(screenId: String) { }
16 |
17 | /**
18 | * Called when No-Code screen starts executing an action.
19 | *
20 | * @param action action that is being executed.
21 | */
22 | fun onActionStartedExecuting(action: QAction) { }
23 |
24 | /**
25 | * Called when No-Code screen fails executing an action.
26 | *
27 | * @param action failed action.
28 | */
29 | fun onActionFailedToExecute(action: QAction) { }
30 |
31 | /**
32 | * Called when No-Code screen finishes executing an action.
33 | *
34 | * @param action executed action.
35 | * For instance, if the user made a purchase then action.type = [QAction.Type.Purchase].
36 | * You can use the [Qonversion.checkEntitlements] method to get available permissions.
37 | */
38 | fun onActionFinishedExecuting(action: QAction) { }
39 |
40 | /**
41 | * Called when No-Code flow is finished and the screen is closed.
42 | */
43 | fun onFinished() { }
44 |
45 | /**
46 | * Called when No-Code screen fails to load.
47 | * Don't forget to close the screen using [NoCodes.close] method.
48 | * @param error The error that occurred while loading the screen.
49 | */
50 | fun onScreenFailedToLoad(error: NoCodesError) { }
51 | }
52 |
--------------------------------------------------------------------------------
/nocodes/src/main/java/io/qonversion/nocodes/interfaces/ScreenCustomizationDelegate.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.nocodes.interfaces
2 |
3 | import io.qonversion.nocodes.dto.QScreenPresentationConfig
4 |
5 | /**
6 | * The delegate is responsible for customizing screens representation.
7 | */
8 | interface ScreenCustomizationDelegate {
9 | /**
10 | * The function should return the screen presentation configuration
11 | * used to present the first screen in the chain.
12 | * @param contextKey the context key of the screen, for which the configuration will be used.
13 | * @return screen presentation configuration.
14 | */
15 | fun getPresentationConfigurationForScreen(contextKey: String): QScreenPresentationConfig
16 | }
17 |
--------------------------------------------------------------------------------
/nocodes/src/main/java/io/qonversion/nocodes/internal/common/BaseClass.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.nocodes.internal.common
2 |
3 | import io.qonversion.nocodes.internal.logger.Logger
4 |
5 | internal abstract class BaseClass(
6 | protected val logger: Logger
7 | )
8 |
--------------------------------------------------------------------------------
/nocodes/src/main/java/io/qonversion/nocodes/internal/common/StorageConstants.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.nocodes.internal.common
2 |
3 | const val PREFS_NAME = "io.qonversion.nocodes"
4 |
5 | internal enum class StorageConstants(val key: String) {
6 | SourceKey("source"),
7 | VersionKey("sourceVersion"),
8 | }
9 |
--------------------------------------------------------------------------------
/nocodes/src/main/java/io/qonversion/nocodes/internal/common/internalConstants.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.nocodes.internal.common
2 |
3 | internal const val API_URL = "https://api.qonversion.io/"
4 |
5 | internal const val PLATFORM = "android"
6 |
--------------------------------------------------------------------------------
/nocodes/src/main/java/io/qonversion/nocodes/internal/common/localStorage/LocalStorage.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.nocodes.internal.common.localStorage
2 |
3 | internal interface LocalStorage {
4 |
5 | fun putInt(key: String, value: Int)
6 |
7 | fun getInt(key: String, defValue: Int): Int
8 |
9 | fun putFloat(key: String, value: Float)
10 |
11 | fun getFloat(key: String, defValue: Float): Float
12 |
13 | fun putLong(key: String, value: Long)
14 |
15 | fun getLong(key: String, defValue: Long): Long
16 |
17 | fun putString(key: String, value: String?)
18 |
19 | fun getString(key: String, defValue: String? = null): String?
20 |
21 | fun remove(key: String)
22 | }
23 |
--------------------------------------------------------------------------------
/nocodes/src/main/java/io/qonversion/nocodes/internal/common/localStorage/SharedPreferencesStorage.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.nocodes.internal.common.localStorage
2 |
3 | import android.content.SharedPreferences
4 |
5 | internal class SharedPreferencesStorage(
6 | private val preferences: SharedPreferences
7 | ) : LocalStorage {
8 | override fun putInt(key: String, value: Int) {
9 | preferences.edit().putInt(key, value).apply()
10 | }
11 |
12 | override fun getInt(key: String, defValue: Int): Int = preferences.getInt(key, defValue)
13 |
14 | override fun putFloat(key: String, value: Float) {
15 | preferences.edit().putFloat(key, value).apply()
16 | }
17 |
18 | override fun getFloat(key: String, defValue: Float): Float = preferences.getFloat(key, defValue)
19 |
20 | override fun putLong(key: String, value: Long) {
21 | preferences.edit().putLong(key, value).apply()
22 | }
23 |
24 | override fun getLong(key: String, defValue: Long): Long = preferences.getLong(key, defValue)
25 |
26 | override fun putString(key: String, value: String?) {
27 | preferences.edit().putString(key, value).apply()
28 | }
29 |
30 | override fun getString(key: String, defValue: String?): String? {
31 | return preferences.getString(key, defValue)
32 | }
33 |
34 | override fun remove(key: String) {
35 | return preferences.edit().remove(key).apply()
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/nocodes/src/main/java/io/qonversion/nocodes/internal/common/mappers/ActionMapper.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.nocodes.internal.common.mappers
2 |
3 | import io.qonversion.nocodes.dto.QAction
4 |
5 | internal class ActionMapper : Mapper {
6 |
7 | override fun fromMap(data: Map<*, *>): QAction {
8 | val typeString = data.getString("type")
9 | val type = QAction.Type.from(typeString)
10 |
11 | var parameters: MutableMap? = null
12 | if (data.containsKey("parameters")) {
13 | parameters = mutableMapOf()
14 | val parametersMap = data["parameters"] as? Map<*, *>
15 | parametersMap?.forEach { (key, value) ->
16 | key.takeIf { it is String }
17 | ?.let { QAction.Parameter.from(it as String) }
18 | ?.let { parameter ->
19 | value?.let { parameters[parameter] = value }
20 | }
21 | }
22 | }
23 |
24 | return QAction(
25 | type,
26 | parameters,
27 | )
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/nocodes/src/main/java/io/qonversion/nocodes/internal/common/mappers/Mapper.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.nocodes.internal.common.mappers
2 |
3 | internal interface Mapper {
4 | @Throws(IllegalStateException::class, NotImplementedError::class)
5 | fun toMap(value: T): Map { throw NotImplementedError() }
6 |
7 | @Throws(IllegalStateException::class)
8 | fun fromMap(data: Map<*, *>): T
9 |
10 | @Throws(IllegalStateException::class)
11 | fun fromList(data: List<*>): List {
12 | return data.mapNotNull { item ->
13 | (item as? Map<*, *>)?.let {
14 | fromMap(it)
15 | }
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/nocodes/src/main/java/io/qonversion/nocodes/internal/common/mappers/ScreenMapper.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.nocodes.internal.common.mappers
2 |
3 | import io.qonversion.nocodes.internal.dto.NoCodeScreen
4 |
5 | internal class ScreenMapper : Mapper {
6 |
7 | override fun fromMap(data: Map<*, *>): NoCodeScreen? {
8 | val id = data.getString("id")
9 | val body = data.getString("body")
10 | val contextKey = data.getString("context_key")
11 |
12 | if (id == null || body == null || contextKey == null) {
13 | return null
14 | }
15 |
16 | return NoCodeScreen(
17 | id,
18 | body,
19 | contextKey,
20 | )
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/nocodes/src/main/java/io/qonversion/nocodes/internal/common/mappers/error/ApiErrorMapper.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.nocodes.internal.common.mappers.error
2 |
3 | import io.qonversion.nocodes.internal.common.mappers.getString
4 | import io.qonversion.nocodes.internal.networkLayer.dto.Response
5 |
6 | internal class ApiErrorMapper : ErrorResponseMapper {
7 |
8 | override fun fromMap(data: Map<*, *>, code: Int): Response.Error {
9 | val message = data.getString("message") ?: "No message provided"
10 | val type = data.getString("type")
11 | val apiCode = data.getString("code")
12 |
13 | return Response.Error(code, message, type, apiCode)
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/nocodes/src/main/java/io/qonversion/nocodes/internal/common/mappers/error/ErrorResponseMapper.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.nocodes.internal.common.mappers.error
2 |
3 | import io.qonversion.nocodes.internal.networkLayer.dto.Response
4 |
5 | internal interface ErrorResponseMapper {
6 |
7 | fun fromMap(data: Map<*, *>, code: Int): Response.Error
8 | }
9 |
--------------------------------------------------------------------------------
/nocodes/src/main/java/io/qonversion/nocodes/internal/common/mappers/extensions.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.nocodes.internal.common.mappers
2 |
3 | import java.util.Date
4 |
5 | internal fun Map<*, *>.getMap(key: String): Map<*, *>? {
6 | return this[key] as? Map<*, *>
7 | }
8 |
9 | internal fun Map<*, *>.getList(key: String): List<*>? {
10 | return this[key] as? List<*>
11 | }
12 |
13 | internal fun Map<*, *>.getBoolean(key: String): Boolean? {
14 | return this[key] as? Boolean
15 | }
16 |
17 | internal fun Map<*, *>.getInt(key: String): Int? {
18 | return this[key] as? Int
19 | }
20 |
21 | internal fun Map<*, *>.getString(key: String): String? {
22 | return this[key] as? String
23 | }
24 |
25 | internal fun Map<*, *>.getFloat(key: String): Float? {
26 | return this[key] as? Float
27 | }
28 |
29 | internal fun Map<*, *>.getDate(key: String): Date? {
30 | val timestamp: Long? = this[key] as? Long
31 |
32 | return timestamp?.let {
33 | Date(it)
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/nocodes/src/main/java/io/qonversion/nocodes/internal/common/serializers/Serializer.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.nocodes.internal.common.serializers
2 |
3 | import io.qonversion.nocodes.error.NoCodesException
4 |
5 | internal interface Serializer {
6 |
7 | @Throws(NoCodesException::class)
8 | fun serialize(data: Map): String
9 |
10 | @Throws(NoCodesException::class)
11 | fun deserialize(payload: String): Any
12 | }
13 |
--------------------------------------------------------------------------------
/nocodes/src/main/java/io/qonversion/nocodes/internal/di/controllers/ControllersAssembly.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.nocodes.internal.di.controllers
2 |
3 | import io.qonversion.nocodes.internal.screen.controller.ScreenController
4 | import io.qonversion.nocodes.internal.screen.view.ScreenContract
5 |
6 | internal interface ControllersAssembly {
7 |
8 | fun screenController(): ScreenController
9 |
10 | fun screenPresenter(view: ScreenContract.View): ScreenContract.Presenter
11 | }
12 |
--------------------------------------------------------------------------------
/nocodes/src/main/java/io/qonversion/nocodes/internal/di/controllers/ControllersAssemblyImpl.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.nocodes.internal.di.controllers
2 |
3 | import android.content.Context
4 | import io.qonversion.nocodes.internal.di.mappers.MappersAssembly
5 | import io.qonversion.nocodes.internal.di.misc.MiscAssembly
6 | import io.qonversion.nocodes.internal.di.services.ServicesAssembly
7 | import io.qonversion.nocodes.internal.dto.config.InternalConfig
8 | import io.qonversion.nocodes.internal.screen.controller.ScreenController
9 | import io.qonversion.nocodes.internal.screen.controller.ScreenControllerImpl
10 | import io.qonversion.nocodes.internal.screen.view.ScreenContract
11 | import io.qonversion.nocodes.internal.screen.view.ScreenPresenter
12 |
13 | internal class ControllersAssemblyImpl(
14 | private val servicesAssembly: ServicesAssembly,
15 | private val miscAssembly: MiscAssembly,
16 | private val mappersAssembly: MappersAssembly,
17 | private val internalConfig: InternalConfig,
18 | private val appContext: Context
19 | ) : ControllersAssembly {
20 |
21 | override fun screenController(): ScreenController {
22 | return ScreenControllerImpl(
23 | internalConfig,
24 | miscAssembly.activityProvider(),
25 | appContext,
26 | miscAssembly.logger()
27 | )
28 | }
29 |
30 | override fun screenPresenter(view: ScreenContract.View): ScreenContract.Presenter {
31 | return ScreenPresenter(
32 | servicesAssembly.screenService(),
33 | view,
34 | miscAssembly.logger(),
35 | miscAssembly.noCodesDelegateProvider(),
36 | miscAssembly.jsonSerializer(),
37 | mappersAssembly.actionMapper(),
38 | )
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/nocodes/src/main/java/io/qonversion/nocodes/internal/di/mappers/MappersAssembly.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.nocodes.internal.di.mappers
2 |
3 | import io.qonversion.nocodes.dto.QAction
4 | import io.qonversion.nocodes.internal.common.mappers.Mapper
5 | import io.qonversion.nocodes.internal.common.mappers.error.ErrorResponseMapper
6 | import io.qonversion.nocodes.internal.dto.NoCodeScreen
7 |
8 | internal interface MappersAssembly {
9 |
10 | fun apiErrorMapper(): ErrorResponseMapper
11 |
12 | fun screenMapper(): Mapper
13 |
14 | fun actionMapper(): Mapper
15 | }
16 |
--------------------------------------------------------------------------------
/nocodes/src/main/java/io/qonversion/nocodes/internal/di/mappers/MappersAssemblyImpl.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.nocodes.internal.di.mappers
2 |
3 | import io.qonversion.nocodes.dto.QAction
4 | import io.qonversion.nocodes.internal.common.mappers.ActionMapper
5 | import io.qonversion.nocodes.internal.common.mappers.Mapper
6 | import io.qonversion.nocodes.internal.common.mappers.ScreenMapper
7 | import io.qonversion.nocodes.internal.common.mappers.error.ApiErrorMapper
8 | import io.qonversion.nocodes.internal.common.mappers.error.ErrorResponseMapper
9 | import io.qonversion.nocodes.internal.dto.NoCodeScreen
10 |
11 | internal class MappersAssemblyImpl : MappersAssembly {
12 |
13 | override fun apiErrorMapper(): ErrorResponseMapper = ApiErrorMapper()
14 |
15 | override fun screenMapper(): Mapper = ScreenMapper()
16 |
17 | override fun actionMapper(): Mapper = ActionMapper()
18 | }
19 |
--------------------------------------------------------------------------------
/nocodes/src/main/java/io/qonversion/nocodes/internal/di/misc/MiscAssembly.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.nocodes.internal.di.misc
2 |
3 | import io.qonversion.nocodes.internal.common.serializers.Serializer
4 | import io.qonversion.nocodes.internal.logger.Logger
5 | import io.qonversion.nocodes.internal.networkLayer.retryDelayCalculator.RetryDelayCalculator
6 | import io.qonversion.nocodes.internal.provider.NoCodesDelegateProvider
7 | import io.qonversion.nocodes.internal.screen.misc.ActivityProvider
8 | import java.util.Locale
9 |
10 | internal interface MiscAssembly {
11 |
12 | fun logger(): Logger
13 |
14 | fun locale(): Locale
15 |
16 | fun jsonSerializer(): Serializer
17 |
18 | fun exponentialDelayCalculator(): RetryDelayCalculator
19 |
20 | fun activityProvider(): ActivityProvider
21 |
22 | fun noCodesDelegateProvider(): NoCodesDelegateProvider
23 | }
24 |
--------------------------------------------------------------------------------
/nocodes/src/main/java/io/qonversion/nocodes/internal/di/misc/MiscAssemblyImpl.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.nocodes.internal.di.misc
2 |
3 | import android.app.Application
4 | import io.qonversion.nocodes.internal.common.serializers.JsonSerializer
5 | import io.qonversion.nocodes.internal.common.serializers.Serializer
6 | import io.qonversion.nocodes.internal.dto.config.InternalConfig
7 | import io.qonversion.nocodes.internal.logger.ConsoleLogger
8 | import io.qonversion.nocodes.internal.logger.Logger
9 | import io.qonversion.nocodes.internal.networkLayer.retryDelayCalculator.ExponentialDelayCalculator
10 | import io.qonversion.nocodes.internal.networkLayer.retryDelayCalculator.RetryDelayCalculator
11 | import io.qonversion.nocodes.internal.provider.NoCodesDelegateProvider
12 | import io.qonversion.nocodes.internal.screen.misc.ActivityProvider
13 | import kotlin.random.Random
14 | import java.util.Locale
15 |
16 | internal class MiscAssemblyImpl(
17 | private val application: Application,
18 | private val internalConfig: InternalConfig
19 | ) : MiscAssembly {
20 |
21 | private val activityProvider: ActivityProvider by lazy {
22 | ActivityProvider(application)
23 | }
24 |
25 | override fun logger(): Logger = ConsoleLogger(internalConfig)
26 |
27 | override fun locale(): Locale = Locale.getDefault()
28 |
29 | override fun jsonSerializer(): Serializer = JsonSerializer()
30 |
31 | override fun exponentialDelayCalculator(): RetryDelayCalculator =
32 | ExponentialDelayCalculator(Random)
33 |
34 | override fun activityProvider(): ActivityProvider = activityProvider
35 |
36 | override fun noCodesDelegateProvider(): NoCodesDelegateProvider = internalConfig
37 | }
38 |
--------------------------------------------------------------------------------
/nocodes/src/main/java/io/qonversion/nocodes/internal/di/network/NetworkAssembly.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.nocodes.internal.di.network
2 |
3 | import io.qonversion.nocodes.internal.networkLayer.apiInteractor.ApiInteractor
4 | import io.qonversion.nocodes.internal.networkLayer.headerBuilder.HeaderBuilder
5 | import io.qonversion.nocodes.internal.networkLayer.networkClient.NetworkClient
6 | import io.qonversion.nocodes.internal.networkLayer.requestConfigurator.RequestConfigurator
7 |
8 | internal interface NetworkAssembly {
9 |
10 | fun networkClient(): NetworkClient
11 |
12 | fun headerBuilder(): HeaderBuilder
13 |
14 | fun requestConfigurator(): RequestConfigurator
15 |
16 | fun exponentialApiInteractor(): ApiInteractor
17 |
18 | fun infiniteExponentialApiInteractor(): ApiInteractor
19 | }
20 |
--------------------------------------------------------------------------------
/nocodes/src/main/java/io/qonversion/nocodes/internal/di/services/ServicesAssembly.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.nocodes.internal.di.services
2 |
3 | import io.qonversion.nocodes.internal.screen.service.ScreenService
4 |
5 | internal interface ServicesAssembly {
6 |
7 | fun screenService(): ScreenService
8 | }
9 |
--------------------------------------------------------------------------------
/nocodes/src/main/java/io/qonversion/nocodes/internal/di/services/ServicesAssemblyImpl.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.nocodes.internal.di.services
2 |
3 | import io.qonversion.nocodes.internal.di.mappers.MappersAssembly
4 | import io.qonversion.nocodes.internal.di.misc.MiscAssembly
5 | import io.qonversion.nocodes.internal.di.network.NetworkAssembly
6 | import io.qonversion.nocodes.internal.screen.service.ScreenService
7 | import io.qonversion.nocodes.internal.screen.service.ScreenServiceImpl
8 |
9 | internal class ServicesAssemblyImpl(
10 | private val mappersAssembly: MappersAssembly,
11 | private val networkAssembly: NetworkAssembly,
12 | private val miscAssembly: MiscAssembly
13 | ) : ServicesAssembly {
14 |
15 | override fun screenService(): ScreenService {
16 | return ScreenServiceImpl(
17 | networkAssembly.requestConfigurator(),
18 | networkAssembly.exponentialApiInteractor(),
19 | mappersAssembly.screenMapper(),
20 | miscAssembly.logger()
21 | )
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/nocodes/src/main/java/io/qonversion/nocodes/internal/di/storage/StorageAssembly.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.nocodes.internal.di.storage
2 |
3 | import android.content.SharedPreferences
4 | import io.qonversion.nocodes.internal.common.localStorage.LocalStorage
5 |
6 | internal interface StorageAssembly {
7 |
8 | fun sharedPreferences(): SharedPreferences
9 |
10 | fun sharedPreferencesStorage(): LocalStorage
11 | }
12 |
--------------------------------------------------------------------------------
/nocodes/src/main/java/io/qonversion/nocodes/internal/di/storage/StorageAssemblyImpl.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.nocodes.internal.di.storage
2 |
3 | import android.content.Context
4 | import android.content.SharedPreferences
5 | import io.qonversion.nocodes.internal.common.PREFS_NAME
6 | import io.qonversion.nocodes.internal.common.localStorage.LocalStorage
7 | import io.qonversion.nocodes.internal.common.localStorage.SharedPreferencesStorage
8 |
9 | internal class StorageAssemblyImpl(
10 | private val context: Context
11 | ) : StorageAssembly {
12 | override fun sharedPreferences(): SharedPreferences =
13 | context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
14 |
15 | override fun sharedPreferencesStorage(): LocalStorage =
16 | SharedPreferencesStorage(sharedPreferences())
17 | }
18 |
--------------------------------------------------------------------------------
/nocodes/src/main/java/io/qonversion/nocodes/internal/dto/NoCodeScreen.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.nocodes.internal.dto
2 |
3 | internal data class NoCodeScreen(
4 | val id: String,
5 | val body: String,
6 | val contextKey: String
7 | )
8 |
--------------------------------------------------------------------------------
/nocodes/src/main/java/io/qonversion/nocodes/internal/dto/config/InternalConfig.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.nocodes.internal.dto.config
2 |
3 | import io.qonversion.nocodes.NoCodesConfig
4 | import io.qonversion.nocodes.dto.LogLevel
5 | import io.qonversion.nocodes.interfaces.NoCodesDelegate
6 | import io.qonversion.nocodes.interfaces.ScreenCustomizationDelegate
7 | import io.qonversion.nocodes.internal.provider.LoggerConfigProvider
8 | import io.qonversion.nocodes.internal.provider.NetworkConfigHolder
9 | import io.qonversion.nocodes.internal.provider.NoCodesDelegateProvider
10 | import io.qonversion.nocodes.internal.provider.PrimaryConfigProvider
11 | import java.lang.ref.WeakReference
12 |
13 | internal class InternalConfig(
14 | override var primaryConfig: PrimaryConfig,
15 | val networkConfig: NetworkConfig,
16 | var loggerConfig: LoggerConfig,
17 | override var noCodesDelegate: WeakReference?,
18 | var screenCustomizationDelegate: WeakReference?,
19 | ) : PrimaryConfigProvider,
20 | LoggerConfigProvider,
21 | NetworkConfigHolder,
22 | NoCodesDelegateProvider {
23 |
24 | constructor(noCodesConfig: NoCodesConfig) : this(
25 | noCodesConfig.primaryConfig,
26 | noCodesConfig.networkConfig,
27 | noCodesConfig.loggerConfig,
28 | noCodesConfig.noCodesDelegate?.let { WeakReference(it) },
29 | noCodesConfig.screenCustomizationDelegate?.let { WeakReference(it) }
30 | )
31 |
32 | override var canSendRequests: Boolean
33 | get() = networkConfig.canSendRequests
34 | set(value) {
35 | networkConfig.canSendRequests = value
36 | }
37 |
38 | override val logLevel: LogLevel
39 | get() = loggerConfig.logLevel
40 |
41 | override val logTag: String
42 | get() = loggerConfig.logTag
43 | }
44 |
--------------------------------------------------------------------------------
/nocodes/src/main/java/io/qonversion/nocodes/internal/dto/config/LoggerConfig.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.nocodes.internal.dto.config
2 |
3 | import io.qonversion.nocodes.dto.LogLevel
4 |
5 | data class LoggerConfig(
6 | val logLevel: LogLevel,
7 | val logTag: String
8 | )
9 |
--------------------------------------------------------------------------------
/nocodes/src/main/java/io/qonversion/nocodes/internal/dto/config/NetworkConfig.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.nocodes.internal.dto.config
2 |
3 | internal class NetworkConfig(val proxyUrl: String?) {
4 | var canSendRequests: Boolean = true
5 | }
6 |
--------------------------------------------------------------------------------
/nocodes/src/main/java/io/qonversion/nocodes/internal/dto/config/PrimaryConfig.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.nocodes.internal.dto.config
2 |
3 | import io.qonversion.nocodes.BuildConfig
4 |
5 | internal data class PrimaryConfig(
6 | val projectKey: String,
7 | val sdkVersion: String = BuildConfig.VERSION_NAME
8 | )
9 |
--------------------------------------------------------------------------------
/nocodes/src/main/java/io/qonversion/nocodes/internal/logger/ConsoleLogger.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.nocodes.internal.logger
2 |
3 | import android.util.Log
4 | import io.qonversion.nocodes.dto.LogLevel
5 | import io.qonversion.nocodes.internal.provider.LoggerConfigProvider
6 |
7 | internal class ConsoleLogger(private val loggerConfigProvider: LoggerConfigProvider) : Logger {
8 | private val logLevel get() = loggerConfigProvider.logLevel.level
9 | private val tag get() = loggerConfigProvider.logTag
10 |
11 | override fun verbose(message: String) {
12 | if (logLevel == LogLevel.Verbose.level) {
13 | Log.v(tag, format(message))
14 | }
15 | }
16 |
17 | override fun info(message: String, throwable: Throwable?) {
18 | if (logLevel <= LogLevel.Info.level) {
19 | Log.i(tag, format(message), throwable)
20 | }
21 | }
22 |
23 | override fun warn(message: String, throwable: Throwable?) {
24 | if (logLevel <= LogLevel.Warning.level) {
25 | Log.w(tag, format(message), throwable)
26 | }
27 | }
28 |
29 | override fun error(message: String, throwable: Throwable?) {
30 | if (logLevel <= LogLevel.Error.level) {
31 | Log.e(tag, format(message), throwable)
32 | }
33 | }
34 |
35 | private fun format(message: String): String {
36 | return "Thread - " + Thread.currentThread().name + " " + message
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/nocodes/src/main/java/io/qonversion/nocodes/internal/logger/Logger.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.nocodes.internal.logger
2 |
3 | internal interface Logger {
4 |
5 | fun verbose(message: String)
6 |
7 | fun info(message: String, throwable: Throwable? = null)
8 |
9 | fun warn(message: String, throwable: Throwable? = null)
10 |
11 | fun error(message: String, throwable: Throwable? = null)
12 | }
13 |
--------------------------------------------------------------------------------
/nocodes/src/main/java/io/qonversion/nocodes/internal/networkLayer/ApiHeader.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.nocodes.internal.networkLayer
2 |
3 | internal enum class ApiHeader(val key: String) {
4 | ContentType("Content-Type"),
5 | Authorization("Authorization"),
6 | Locale("User-Locale"),
7 | Source("Source"),
8 | SourceVersion("Source-Version"),
9 | Platform("Platform"),
10 | PlatformVersion("Platform-Version"),
11 | UserID("User-Id")
12 | }
13 |
--------------------------------------------------------------------------------
/nocodes/src/main/java/io/qonversion/nocodes/internal/networkLayer/RetryPolicy.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.nocodes.internal.networkLayer
2 |
3 | private const val DEFAULT_RETRY_COUNT = 3
4 | private const val DEFAULT_MIN_DELAY_MS = 500L
5 |
6 | internal sealed class RetryPolicy {
7 | object None : RetryPolicy()
8 |
9 | class Exponential(
10 | val retryCount: Int = DEFAULT_RETRY_COUNT,
11 | val minDelay: Long = DEFAULT_MIN_DELAY_MS
12 | ) : RetryPolicy()
13 |
14 | class InfiniteExponential(
15 | val minDelay: Long = DEFAULT_MIN_DELAY_MS
16 | ) : RetryPolicy()
17 | }
18 |
--------------------------------------------------------------------------------
/nocodes/src/main/java/io/qonversion/nocodes/internal/networkLayer/apiInteractor/ApiInteractor.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.nocodes.internal.networkLayer.apiInteractor
2 |
3 | import io.qonversion.nocodes.error.NoCodesException
4 | import io.qonversion.nocodes.internal.networkLayer.RetryPolicy
5 | import io.qonversion.nocodes.internal.networkLayer.dto.Request
6 | import io.qonversion.nocodes.internal.networkLayer.dto.Response
7 |
8 | internal interface ApiInteractor {
9 | @Throws(NoCodesException::class)
10 | suspend fun execute(request: Request): Response
11 |
12 | @Throws(NoCodesException::class)
13 | suspend fun execute(request: Request, retryPolicy: RetryPolicy): Response
14 | }
15 |
--------------------------------------------------------------------------------
/nocodes/src/main/java/io/qonversion/nocodes/internal/networkLayer/dto/RawResponse.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.nocodes.internal.networkLayer.dto
2 |
3 | import io.qonversion.nocodes.internal.networkLayer.utils.isInternalServerErrorHttpCode
4 | import io.qonversion.nocodes.internal.networkLayer.utils.isSuccessHttpCode
5 |
6 | internal class RawResponse(
7 | val code: Int,
8 | val payload: Any // Array or Map
9 | ) {
10 | val isSuccess: Boolean = code.isSuccessHttpCode
11 | val isInternalServerError: Boolean = code.isInternalServerErrorHttpCode
12 | }
13 |
--------------------------------------------------------------------------------
/nocodes/src/main/java/io/qonversion/nocodes/internal/networkLayer/dto/Request.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.nocodes.internal.networkLayer.dto
2 |
3 | internal class Request private constructor(
4 | val url: String,
5 | val type: Type,
6 | val headers: Map,
7 | val body: Map? = null
8 | ) {
9 | companion object {
10 | fun post(
11 | url: String,
12 | headers: Map,
13 | body: Map
14 | ) = Request(url, Type.POST, headers, body)
15 |
16 | fun get(url: String, headers: Map) = Request(url, Type.GET, headers)
17 | }
18 |
19 | enum class Type {
20 | POST,
21 | GET,
22 | DELETE,
23 | PUT
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/nocodes/src/main/java/io/qonversion/nocodes/internal/networkLayer/dto/Response.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.nocodes.internal.networkLayer.dto
2 |
3 | import io.qonversion.nocodes.error.ErrorCode
4 | import io.qonversion.nocodes.error.NoCodesException
5 |
6 | internal sealed class Response(
7 | val code: Int
8 | ) {
9 | class Error(
10 | code: Int,
11 | val message: String,
12 | val type: String? = null,
13 | val apiCode: String? = null
14 | ) : Response(code)
15 |
16 | class Success(code: Int, val data: Any) : Response(code) {
17 |
18 | val mapData: Map<*, *> get() = getTypedData()
19 |
20 | val arrayData: List<*> get() = getTypedData()
21 |
22 | @Throws(NoCodesException::class)
23 | inline fun getTypedData(): T {
24 | return if (data is T) {
25 | data
26 | } else {
27 | throw NoCodesException(ErrorCode.Mapping, "Unexpected data type.")
28 | }
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/nocodes/src/main/java/io/qonversion/nocodes/internal/networkLayer/headerBuilder/HeaderBuilder.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.nocodes.internal.networkLayer.headerBuilder
2 |
3 | internal interface HeaderBuilder {
4 |
5 | fun buildCommonHeaders(): Map
6 | }
7 |
--------------------------------------------------------------------------------
/nocodes/src/main/java/io/qonversion/nocodes/internal/networkLayer/headerBuilder/HeaderBuilderImpl.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.nocodes.internal.networkLayer.headerBuilder
2 |
3 | import android.os.Build
4 | import io.qonversion.nocodes.internal.common.PLATFORM
5 | import io.qonversion.nocodes.internal.common.StorageConstants
6 | import io.qonversion.nocodes.internal.common.localStorage.LocalStorage
7 | import io.qonversion.nocodes.internal.networkLayer.ApiHeader
8 | import io.qonversion.nocodes.internal.provider.PrimaryConfigProvider
9 | import java.util.Locale
10 |
11 | internal class HeaderBuilderImpl(
12 | private val localStorage: LocalStorage,
13 | private val locale: Locale,
14 | private val primaryConfigProvider: PrimaryConfigProvider
15 | ) : HeaderBuilder {
16 |
17 | private val source by lazy {
18 | localStorage.getString(StorageConstants.SourceKey.key, PLATFORM) ?: PLATFORM
19 | }
20 |
21 | private val sourceVersion by lazy {
22 | localStorage.getString(
23 | StorageConstants.VersionKey.key,
24 | primaryConfigProvider.primaryConfig.sdkVersion
25 | )
26 | ?: primaryConfigProvider.primaryConfig.sdkVersion
27 | }
28 |
29 | override fun buildCommonHeaders(): Map {
30 | val locale = locale.language
31 | val projectKey = primaryConfigProvider.primaryConfig.projectKey
32 | val bearer = "Bearer $projectKey"
33 |
34 | return mapOf(
35 | ApiHeader.Authorization.key to bearer,
36 | ApiHeader.Locale.key to locale,
37 | ApiHeader.Source.key to source,
38 | ApiHeader.SourceVersion.key to sourceVersion,
39 | ApiHeader.Platform.key to PLATFORM,
40 | ApiHeader.PlatformVersion.key to Build.VERSION.RELEASE
41 | )
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/nocodes/src/main/java/io/qonversion/nocodes/internal/networkLayer/networkClient/NetworkClient.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.nocodes.internal.networkLayer.networkClient
2 |
3 | import io.qonversion.nocodes.error.NoCodesException
4 | import io.qonversion.nocodes.internal.networkLayer.dto.RawResponse
5 | import io.qonversion.nocodes.internal.networkLayer.dto.Request
6 |
7 | internal interface NetworkClient {
8 | @Throws(NoCodesException::class)
9 | suspend fun execute(request: Request): RawResponse
10 | }
11 |
--------------------------------------------------------------------------------
/nocodes/src/main/java/io/qonversion/nocodes/internal/networkLayer/requestConfigurator/ApiEndpoint.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.nocodes.internal.networkLayer.requestConfigurator
2 |
3 | internal enum class ApiEndpoint(val path: String) {
4 | Screens("screens"),
5 | Contexts("contexts")
6 | }
7 |
--------------------------------------------------------------------------------
/nocodes/src/main/java/io/qonversion/nocodes/internal/networkLayer/requestConfigurator/RequestConfigurator.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.nocodes.internal.networkLayer.requestConfigurator
2 |
3 | import io.qonversion.nocodes.internal.networkLayer.dto.Request
4 |
5 | internal interface RequestConfigurator {
6 |
7 | fun configureScreenRequest(contextKey: String): Request
8 |
9 | fun configureScreenRequestById(screenId: String): Request
10 | }
11 |
--------------------------------------------------------------------------------
/nocodes/src/main/java/io/qonversion/nocodes/internal/networkLayer/requestConfigurator/RequestConfiguratorImpl.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.nocodes.internal.networkLayer.requestConfigurator
2 |
3 | import io.qonversion.nocodes.internal.networkLayer.dto.Request
4 | import io.qonversion.nocodes.internal.networkLayer.headerBuilder.HeaderBuilder
5 |
6 | internal class RequestConfiguratorImpl(
7 | private val headerBuilder: HeaderBuilder,
8 | private val baseUrl: String,
9 | ) : RequestConfigurator {
10 |
11 | override fun configureScreenRequest(contextKey: String): Request {
12 | val headers = headerBuilder.buildCommonHeaders()
13 |
14 | return Request.get("${baseUrl}v3/${ApiEndpoint.Contexts.path}/$contextKey/${ApiEndpoint.Screens.path}", headers)
15 | }
16 |
17 | override fun configureScreenRequestById(screenId: String): Request {
18 | val headers = headerBuilder.buildCommonHeaders()
19 |
20 | return Request.get("${baseUrl}v3/${ApiEndpoint.Screens.path}/$screenId", headers)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/nocodes/src/main/java/io/qonversion/nocodes/internal/networkLayer/retryDelayCalculator/ExponentialDelayCalculator.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.nocodes.internal.networkLayer.retryDelayCalculator
2 |
3 | import io.qonversion.nocodes.internal.utils.MS_IN_SEC
4 | import kotlin.math.min
5 | import kotlin.math.pow
6 | import kotlin.math.roundToLong
7 | import kotlin.random.Random
8 |
9 | private const val JITTER = 0.4f
10 | private const val FACTOR = 2.4f
11 | internal const val MAX_DELAY_MS = 1000000L
12 |
13 | internal class ExponentialDelayCalculator(
14 | private val randomizer: Random
15 | ) : RetryDelayCalculator {
16 | private val jitter = JITTER
17 | private val factor = FACTOR
18 | private val maxDelayMS = MAX_DELAY_MS
19 |
20 | @Throws(IllegalArgumentException::class)
21 | override fun countDelay(minDelay: Long, retriesCount: Int): Long {
22 | var delay: Long = (minDelay + factor.pow(retriesCount) * MS_IN_SEC).toLong()
23 | val delta: Long = (delay * jitter).roundToLong()
24 |
25 | delay += randomizer.nextLong(delta + 1)
26 | // On big attempt indexes may overflow long bounds and become negative.
27 | if (delay < 0) delay = maxDelayMS
28 |
29 | return min(delay, maxDelayMS)
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/nocodes/src/main/java/io/qonversion/nocodes/internal/networkLayer/retryDelayCalculator/RetryDelayCalculator.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.nocodes.internal.networkLayer.retryDelayCalculator
2 |
3 | internal interface RetryDelayCalculator {
4 | @Throws(IllegalArgumentException::class)
5 | fun countDelay(minDelay: Long, retriesCount: Int): Long
6 | }
7 |
--------------------------------------------------------------------------------
/nocodes/src/main/java/io/qonversion/nocodes/internal/networkLayer/utils/extensions.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.nocodes.internal.networkLayer.utils
2 |
3 | import org.json.JSONArray
4 | import org.json.JSONException
5 | import org.json.JSONObject
6 |
7 | @Throws(JSONException::class)
8 | internal fun JSONObject.toMap(): Map {
9 | val map = mutableMapOf()
10 | keys().forEach { key ->
11 | val value = get(key).parseJsonValue()
12 | map[key] = value
13 | }
14 |
15 | return map
16 | }
17 |
18 | @Throws(JSONException::class)
19 | internal fun JSONArray.toList(): List {
20 | val list = mutableListOf()
21 | for (i in 0 until length()) {
22 | val value = get(i).parseJsonValue()
23 | list.add(value)
24 | }
25 |
26 | return list.toList()
27 | }
28 |
29 | @Throws(JSONException::class)
30 | internal fun Any.parseJsonValue() = when (this) {
31 | is JSONArray -> toList()
32 | is JSONObject -> toMap()
33 | else -> this
34 | }
35 |
36 | private const val MIN_SUCCESS_CODE = 200
37 | private const val MAX_SUCCESS_CODE = 299
38 | private const val MIN_INTERNAL_SERVER_ERROR_CODE = 500
39 | private const val MAX_INTERNAL_SERVER_ERROR_CODE = 599
40 |
41 | internal val Int.isSuccessHttpCode: Boolean get() = this in MIN_SUCCESS_CODE..MAX_SUCCESS_CODE
42 | internal val Int.isInternalServerErrorHttpCode: Boolean get() =
43 | this in MIN_INTERNAL_SERVER_ERROR_CODE..MAX_INTERNAL_SERVER_ERROR_CODE
44 |
--------------------------------------------------------------------------------
/nocodes/src/main/java/io/qonversion/nocodes/internal/provider/LoggerConfigProvider.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.nocodes.internal.provider
2 |
3 | import io.qonversion.nocodes.dto.LogLevel
4 |
5 | internal interface LoggerConfigProvider {
6 |
7 | val logLevel: LogLevel
8 |
9 | val logTag: String
10 | }
11 |
--------------------------------------------------------------------------------
/nocodes/src/main/java/io/qonversion/nocodes/internal/provider/NetworkConfigHolder.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.nocodes.internal.provider
2 |
3 | internal interface NetworkConfigHolder {
4 |
5 | var canSendRequests: Boolean
6 | }
7 |
--------------------------------------------------------------------------------
/nocodes/src/main/java/io/qonversion/nocodes/internal/provider/NoCodesDelegateProvider.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.nocodes.internal.provider
2 |
3 | import io.qonversion.nocodes.interfaces.NoCodesDelegate
4 | import java.lang.ref.WeakReference
5 |
6 | internal interface NoCodesDelegateProvider {
7 |
8 | var noCodesDelegate: WeakReference?
9 | }
10 |
--------------------------------------------------------------------------------
/nocodes/src/main/java/io/qonversion/nocodes/internal/provider/PrimaryConfigProvider.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.nocodes.internal.provider
2 |
3 | import io.qonversion.nocodes.internal.dto.config.PrimaryConfig
4 |
5 | internal interface PrimaryConfigProvider {
6 |
7 | val primaryConfig: PrimaryConfig
8 | }
9 |
--------------------------------------------------------------------------------
/nocodes/src/main/java/io/qonversion/nocodes/internal/screen/controller/ScreenController.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.nocodes.internal.screen.controller
2 |
3 | internal interface ScreenController {
4 | suspend fun showScreen(contextKey: String)
5 |
6 | fun close()
7 | }
8 |
--------------------------------------------------------------------------------
/nocodes/src/main/java/io/qonversion/nocodes/internal/screen/misc/ActivityProvider.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.nocodes.internal.screen.misc
2 |
3 | import android.app.Activity
4 | import android.app.Application
5 | import android.os.Bundle
6 | import java.lang.ref.WeakReference
7 |
8 | internal class ActivityProvider(application: Application) : Application.ActivityLifecycleCallbacks {
9 |
10 | private var currentActivity: WeakReference? = null
11 |
12 | init {
13 | application.registerActivityLifecycleCallbacks(this)
14 | }
15 |
16 | fun getCurrentActivity(): Activity? = currentActivity?.get()
17 |
18 | override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) = Unit
19 |
20 | override fun onActivityStarted(activity: Activity) {
21 | currentActivity = WeakReference(activity)
22 | }
23 |
24 | override fun onActivityResumed(activity: Activity) {
25 | currentActivity = WeakReference(activity)
26 | }
27 |
28 | override fun onActivityPaused(activity: Activity) = Unit
29 |
30 | override fun onActivityStopped(activity: Activity) = Unit
31 |
32 | override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) = Unit
33 |
34 | override fun onActivityDestroyed(activity: Activity) = Unit
35 | }
36 |
--------------------------------------------------------------------------------
/nocodes/src/main/java/io/qonversion/nocodes/internal/screen/service/ScreenService.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.nocodes.internal.screen.service
2 |
3 | import io.qonversion.nocodes.error.NoCodesException
4 | import io.qonversion.nocodes.internal.dto.NoCodeScreen
5 |
6 | internal interface ScreenService {
7 |
8 | @Throws(NoCodesException::class)
9 | suspend fun getScreen(contextKey: String): NoCodeScreen
10 |
11 | @Throws(NoCodesException::class)
12 | suspend fun getScreenById(screenId: String): NoCodeScreen
13 | }
14 |
--------------------------------------------------------------------------------
/nocodes/src/main/java/io/qonversion/nocodes/internal/screen/utils.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.nocodes.internal.screen
2 |
3 | import io.qonversion.nocodes.R
4 | import io.qonversion.nocodes.dto.QScreenPresentationStyle
5 |
6 | internal fun getScreenTransactionAnimations(screenPresentationStyle: QScreenPresentationStyle?) =
7 | when (screenPresentationStyle) {
8 | QScreenPresentationStyle.FullScreen -> Pair(
9 | R.anim.nc_slide_in_from_bottom,
10 | R.anim.nc_slide_out_to_bottom
11 | )
12 | QScreenPresentationStyle.NoAnimation -> Pair(0, 0)
13 | else -> null
14 | }
15 |
--------------------------------------------------------------------------------
/nocodes/src/main/java/io/qonversion/nocodes/internal/screen/view/ScreenContract.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.nocodes.internal.screen.view
2 |
3 | import io.qonversion.nocodes.dto.QAction
4 |
5 | internal class ScreenContract {
6 | interface View {
7 | fun displayScreen(screenId: String, html: String)
8 |
9 | fun navigateToScreen(screenId: String)
10 |
11 | fun openLink(url: String)
12 |
13 | fun openDeepLink(url: String)
14 |
15 | fun purchase(productId: String)
16 |
17 | fun restore()
18 |
19 | fun close(action: QAction = QAction(QAction.Type.Close))
20 |
21 | fun closeAll(action: QAction = QAction(QAction.Type.Close))
22 |
23 | fun sendProductsToWebView(jsonData: String)
24 |
25 | fun finishScreenPreparation()
26 | }
27 |
28 | internal interface Presenter {
29 | fun onStart(contextKey: String?, screenId: String?)
30 |
31 | fun onWebViewMessageReceived(message: String)
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/nocodes/src/main/java/io/qonversion/nocodes/internal/utils/dateUtils.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.nocodes.internal.utils
2 |
3 | internal const val MS_IN_SEC = 1000L
4 |
--------------------------------------------------------------------------------
/nocodes/src/main/res/anim/nc_fade_in.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/nocodes/src/main/res/anim/nc_fade_out.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/nocodes/src/main/res/anim/nc_slide_in_from_bottom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
7 |
8 |
--------------------------------------------------------------------------------
/nocodes/src/main/res/anim/nc_slide_in_from_left.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/nocodes/src/main/res/anim/nc_slide_out_to_bottom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
7 |
8 |
--------------------------------------------------------------------------------
/nocodes/src/main/res/anim/nc_slide_out_to_left.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/nocodes/src/main/res/layout/nc_activity_screen.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/nocodes/src/main/res/layout/nc_fragment_screen.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
13 |
17 |
--------------------------------------------------------------------------------
/nocodes/src/main/res/layout/nc_progress_bar.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
14 |
15 |
--------------------------------------------------------------------------------
/nocodes/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #5C5C5C
4 |
--------------------------------------------------------------------------------
/nocodes/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
--------------------------------------------------------------------------------
/sample/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/sample/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 | }
--------------------------------------------------------------------------------
/sample/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/sample/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
15 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/sample/src/main/ic_launcher-playstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qonversion/android-sdk/15d67725d1139ba7217e19f5cee00b4a1867a809/sample/src/main/ic_launcher-playstore.png
--------------------------------------------------------------------------------
/sample/src/main/java/io/qonversion/sample/EntitlementsAdapter.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.sample
2 |
3 | import android.view.LayoutInflater
4 | import android.view.View
5 | import android.view.ViewGroup
6 | import androidx.recyclerview.widget.RecyclerView
7 | import com.qonversion.android.sdk.dto.entitlements.QEntitlement
8 | import io.qonversion.sample.databinding.TableRowEntitlementBinding
9 |
10 | class EntitlementsAdapter(private val entitlements: List) :
11 | RecyclerView.Adapter() {
12 |
13 | private lateinit var binding: TableRowEntitlementBinding
14 |
15 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RowViewHolder {
16 | binding = TableRowEntitlementBinding.inflate(
17 | LayoutInflater.from(parent.context),
18 | parent,
19 | false
20 | )
21 | return RowViewHolder(binding.root)
22 | }
23 |
24 | override fun onBindViewHolder(holder: RowViewHolder, position: Int) =
25 | holder.bind(entitlements[position])
26 |
27 | override fun getItemCount() = entitlements.size
28 |
29 | inner class RowViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
30 | fun bind(entitlement: QEntitlement) = with(itemView) {
31 | binding.txtEntitlementId.text = entitlement.id
32 | binding.txtProductId.text = entitlement.productId
33 | binding.txtRenewStateLabel.text = entitlement.renewState.name
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/sample/src/main/java/io/qonversion/sample/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.sample
2 |
3 | import android.content.Context
4 | import android.content.Intent
5 | import android.os.Bundle
6 | import androidx.appcompat.app.AppCompatActivity
7 | import androidx.navigation.NavController
8 | import androidx.navigation.fragment.NavHostFragment
9 | import androidx.navigation.fragment.findNavController
10 | import androidx.navigation.ui.setupWithNavController
11 | import io.qonversion.sample.databinding.ActivityMainBinding
12 |
13 | class MainActivity : AppCompatActivity() {
14 | private lateinit var navController: NavController
15 |
16 | override fun onCreate(savedInstanceState: Bundle?) {
17 | super.onCreate(savedInstanceState)
18 | val binding = ActivityMainBinding.inflate(layoutInflater)
19 | setContentView(binding.root)
20 |
21 | val navHostFragment =
22 | supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
23 | navController = navHostFragment.findNavController()
24 |
25 | binding.bottomNav.setupWithNavController(navController)
26 | }
27 |
28 | companion object {
29 | fun getCallingIntent(context: Context): Intent {
30 | return Intent(context, MainActivity::class.java)
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/sample/src/main/java/io/qonversion/sample/ProductsAdapter.kt:
--------------------------------------------------------------------------------
1 | package io.qonversion.sample
2 |
3 | import android.view.LayoutInflater
4 | import android.view.View
5 | import android.view.ViewGroup
6 | import androidx.recyclerview.widget.RecyclerView
7 | import com.qonversion.android.sdk.dto.products.QProduct
8 | import io.qonversion.sample.databinding.TableRowProductBinding
9 |
10 | class ProductsAdapter(
11 | private val products: List,
12 | private val onItemClicked: (QProduct) -> Unit
13 | ) : RecyclerView.Adapter() {
14 |
15 | private lateinit var binding: TableRowProductBinding
16 |
17 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RowViewHolder {
18 | binding = TableRowProductBinding.inflate(
19 | LayoutInflater.from(parent.context),
20 | parent,
21 | false
22 | )
23 | return RowViewHolder(binding.root) { index ->
24 | onItemClicked(products[index])
25 | }
26 | }
27 |
28 | override fun onBindViewHolder(holder: RowViewHolder, position: Int) =
29 | holder.bind(products[position])
30 |
31 | override fun getItemCount() = products.size
32 |
33 | inner class RowViewHolder(itemView: View, onItemClicked: (Int) -> Unit) :
34 | RecyclerView.ViewHolder(itemView) {
35 | init {
36 | itemView.setOnClickListener {
37 | onItemClicked(adapterPosition)
38 | }
39 | }
40 |
41 | fun bind(product: QProduct) = with(itemView) {
42 | binding.txtName.text = product.qonversionID
43 | binding.txtDescription.text = product.skuDetail?.description
44 | binding.txtPrice.text = product.skuDetail?.price
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/sample/src/main/res/drawable/ic_entitlement.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/sample/src/main/res/drawable/ic_notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qonversion/android-sdk/15d67725d1139ba7217e19f5cee00b4a1867a809/sample/src/main/res/drawable/ic_notification.png
--------------------------------------------------------------------------------
/sample/src/main/res/drawable/ic_offering.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
17 |
18 |
24 |
25 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/table_row_entitlement.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
19 |
20 |
28 |
29 |
38 |
39 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/table_row_product.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
19 |
20 |
28 |
29 |
38 |
39 |
--------------------------------------------------------------------------------
/sample/src/main/res/menu/drawer_bottom_nav_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qonversion/android-sdk/15d67725d1139ba7217e19f5cee00b4a1867a809/sample/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qonversion/android-sdk/15d67725d1139ba7217e19f5cee00b4a1867a809/sample/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qonversion/android-sdk/15d67725d1139ba7217e19f5cee00b4a1867a809/sample/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qonversion/android-sdk/15d67725d1139ba7217e19f5cee00b4a1867a809/sample/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qonversion/android-sdk/15d67725d1139ba7217e19f5cee00b4a1867a809/sample/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qonversion/android-sdk/15d67725d1139ba7217e19f5cee00b4a1867a809/sample/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qonversion/android-sdk/15d67725d1139ba7217e19f5cee00b4a1867a809/sample/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qonversion/android-sdk/15d67725d1139ba7217e19f5cee00b4a1867a809/sample/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qonversion/android-sdk/15d67725d1139ba7217e19f5cee00b4a1867a809/sample/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qonversion/android-sdk/15d67725d1139ba7217e19f5cee00b4a1867a809/sample/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qonversion/android-sdk/15d67725d1139ba7217e19f5cee00b4a1867a809/sample/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qonversion/android-sdk/15d67725d1139ba7217e19f5cee00b4a1867a809/sample/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qonversion/android-sdk/15d67725d1139ba7217e19f5cee00b4a1867a809/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qonversion/android-sdk/15d67725d1139ba7217e19f5cee00b4a1867a809/sample/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qonversion/android-sdk/15d67725d1139ba7217e19f5cee00b4a1867a809/sample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/sample/src/main/res/navigation/nav_graph.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
16 |
19 |
20 |
25 |
30 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | @color/colorQonversionBlue
4 | @color/colorQonversionBlueDark
5 | #D81B60
6 | #3D5DE1
7 | #344cb4
8 | #FFFFFF
9 | #000000
10 | #34c759
11 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Qonversion Sample Application
3 | Application icon
4 | Welcome to Qonversion sample\u00A0app.
5 | Please use the\u00A0sign-in with\u00A0Google option to\u00A0test cross-platform subscription management.
6 | Skip
7 | Authorize with Google
8 | Entitlement Id
9 | Product Id
10 | Renew state
11 | Active entitlements
12 | Build in-app subscriptions\n without server code
13 | Loading…
14 | Logout
15 | Present Paywall
16 | Name
17 | Description
18 | Price
19 | 11599271839-jg1njos9hp2d07jg7d3oqi523io0pdr6.apps.googleusercontent.com
20 | Subscribe for
21 | Buy for
22 | Nothing to restore
23 | Successfully purchased
24 | Restore purchases
25 | Main Offering products
26 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
12 |
13 |
--------------------------------------------------------------------------------
/scripts/maven-release.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.vanniktech.maven.publish'
2 |
3 | afterEvaluate {
4 | mavenPublishing {
5 | coordinates(PUBLISH_GROUP_ID, PUBLISH_ARTIFACT_ID, android.defaultConfig.versionName)
6 |
7 | pom {
8 | name = POM_NAME
9 | packaging = POM_PACKAGING
10 | description = POM_DESCRIPTION
11 | url = POM_URL
12 | licenses {
13 | license {
14 | name = POM_LICENCE_NAME
15 | url = POM_LICENCE_URL
16 | distribution = POM_LICENCE_DIST
17 | }
18 | }
19 | developers {
20 | developer {
21 | id = POM_DEVELOPER_ID
22 | name = POM_DEVELOPER_NAME
23 | }
24 | }
25 | scm {
26 | url = POM_SCM_URL
27 | connection = POM_SCM_CONNECTION
28 | developerConnection = POM_SCM_DEV_CONNECTION
29 | }
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/sdk/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | .idea
3 |
--------------------------------------------------------------------------------
/sdk/consumer-rules.pro:
--------------------------------------------------------------------------------
1 | -keep class com.qonversion.** { *; }
2 |
3 | # Retrofit obfucation fix for Java 18 + Gradle 8
4 | # todo remove after upgrading retrofit to a version including this commit - https://github.com/square/retrofit/commit/ef8d867ffb34b419355a323e11ba89db1904f8c2
5 | -keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation
6 | -if interface * { @retrofit2.http.* public *** *(...); }
7 | -keep,allowoptimization,allowshrinking,allowobfuscation class <3>
8 | -keep,allowobfuscation,allowshrinking class retrofit2.Response
9 |
--------------------------------------------------------------------------------
/sdk/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | -keep class com.qonversion.** { *; }
2 |
3 | # Retrofit obfucation fix for Java 18 + Gradle 8
4 | # todo remove after upgrading retrofit to a version including this commit - https://github.com/square/retrofit/commit/ef8d867ffb34b419355a323e11ba89db1904f8c2
5 | -keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation
6 | -if interface * { @retrofit2.http.* public *** *(...); }
7 | -keep,allowoptimization,allowshrinking,allowobfuscation class <3>
8 | -keep,allowobfuscation,allowshrinking class retrofit2.Response
9 |
--------------------------------------------------------------------------------
/sdk/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/automations/ScreenCustomizationDelegate.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.automations
2 |
3 | import com.qonversion.android.sdk.automations.dto.QScreenPresentationConfig
4 |
5 | /**
6 | * The delegate is responsible for customizing screens representation.
7 | */
8 | interface ScreenCustomizationDelegate {
9 | /**
10 | * The function should return the screen presentation configuration
11 | * used to present the first screen in the chain.
12 | * @param screenId identifier of the screen, for which the configuration will be used.
13 | * @return screen presentation configuration.
14 | */
15 | fun getPresentationConfigurationForScreen(screenId: String): QScreenPresentationConfig
16 | }
17 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/automations/dto/AutomationsEvent.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.automations.dto
2 |
3 | import java.util.Date
4 |
5 | data class AutomationsEvent(
6 | val type: AutomationsEventType,
7 | val date: Date,
8 | private val productId: String? // Temporarily inaccessible
9 | )
10 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/automations/dto/AutomationsEventType.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.automations.dto
2 |
3 | enum class AutomationsEventType(val type: String) {
4 | Unknown("unknown"),
5 | TrialStarted("trial_started"),
6 | TrialConverted("trial_converted"),
7 | TrialCanceled("trial_canceled"),
8 | TrialBillingRetry("trial_billing_retry_entered"),
9 | SubscriptionStarted("subscription_started"),
10 | SubscriptionRenewed("subscription_renewed"),
11 | SubscriptionRefunded("subscription_refunded"),
12 | SubscriptionCanceled("subscription_canceled"),
13 | SubscriptionBillingRetry("subscription_billing_retry_entered"),
14 | InAppPurchase("in_app_purchase"),
15 | SubscriptionUpgraded("subscription_upgraded"),
16 | TrialStillActive("trial_still_active"),
17 | TrialExpired("trial_expired"),
18 | SubscriptionExpired("subscription_expired"),
19 | SubscriptionDowngraded("subscription_downgraded"),
20 | SubscriptionProductChanged("subscription_product_changed");
21 |
22 | companion object {
23 | fun fromType(type: String?) = values().find { it.type == type } ?: Unknown
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/automations/dto/QActionResult.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.automations.dto
2 |
3 | import com.qonversion.android.sdk.dto.QonversionError
4 |
5 | data class QActionResult(
6 | val type: QActionResultType,
7 | val value: Map? = null
8 | ) {
9 | var error: QonversionError? = null
10 | }
11 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/automations/dto/QActionResultType.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.automations.dto
2 |
3 | enum class QActionResultType(val type: String) {
4 | Unknown("unknown"),
5 | Url("url"),
6 | DeepLink("deeplink"),
7 | Navigation("navigate"),
8 | Purchase("purchase"),
9 | Restore("restore"),
10 | Close("close"),
11 | CloseAll("closeAllQScreens");
12 |
13 | companion object {
14 | fun fromType(type: String?) = values().find { it.type == type } ?: Unknown
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/automations/dto/QScreenPresentationConfig.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.automations.dto
2 |
3 | data class QScreenPresentationConfig(
4 | val presentationStyle: QScreenPresentationStyle = QScreenPresentationStyle.Push
5 | )
6 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/automations/dto/QScreenPresentationStyle.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.automations.dto
2 |
3 | enum class QScreenPresentationStyle {
4 | Push, /** default screen transaction animation will be used */
5 | FullScreen, /** screen will move from bottom to top */
6 | NoAnimation, /** screen will appear/disappear without any animation */
7 | }
8 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/automations/internal/ActivityProvider.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.automations.internal
2 |
3 | import android.app.Activity
4 | import android.app.Application
5 | import android.os.Bundle
6 | import javax.inject.Inject
7 | import java.lang.ref.WeakReference
8 |
9 | class ActivityProvider @Inject constructor(application: Application) : Application.ActivityLifecycleCallbacks {
10 |
11 | private var currentActivity: WeakReference? = null
12 |
13 | init {
14 | application.registerActivityLifecycleCallbacks(this)
15 | }
16 |
17 | fun getCurrentActivity(): Activity? = currentActivity?.get()
18 |
19 | override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) = Unit
20 |
21 | override fun onActivityStarted(activity: Activity) {
22 | currentActivity = WeakReference(activity)
23 | }
24 |
25 | override fun onActivityResumed(activity: Activity) {
26 | currentActivity = WeakReference(activity)
27 | }
28 |
29 | override fun onActivityPaused(activity: Activity) = Unit
30 |
31 | override fun onActivityStopped(activity: Activity) = Unit
32 |
33 | override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) = Unit
34 |
35 | override fun onActivityDestroyed(activity: Activity) = Unit
36 | }
37 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/automations/internal/macros/Macros.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.automations.internal.macros
2 |
3 | internal data class Macros(
4 | val type: MacrosType,
5 | val productID: String,
6 | val originalMacrosString: String
7 | )
8 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/automations/internal/macros/MacrosType.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.automations.internal.macros
2 |
3 | internal enum class MacrosType(val type: String) {
4 | Unknown("unknown"),
5 | Price("price"),
6 | SubscriptionDuration("duration_subscription"),
7 | TrialDuration("duration_trial");
8 |
9 | companion object {
10 | fun fromType(type: String) = values()
11 | .find { it.type == type } ?: Unknown
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/automations/internal/utils.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.automations.internal
2 |
3 | import com.qonversion.android.sdk.R
4 | import com.qonversion.android.sdk.automations.dto.QScreenPresentationStyle
5 |
6 | internal fun getScreenTransactionAnimations(screenPresentationStyle: QScreenPresentationStyle?) =
7 | when (screenPresentationStyle) {
8 | QScreenPresentationStyle.FullScreen -> Pair(
9 | R.anim.q_slide_in_from_bottom,
10 | R.anim.q_slide_out_to_bottom
11 | )
12 | QScreenPresentationStyle.NoAnimation -> Pair(0, 0)
13 | else -> null
14 | }
15 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/automations/mvp/ScreenContract.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.automations.mvp
2 |
3 | import com.qonversion.android.sdk.dto.QonversionError
4 | import com.qonversion.android.sdk.automations.dto.QActionResult
5 | import com.qonversion.android.sdk.automations.dto.QActionResultType
6 |
7 | internal class ScreenContract {
8 | interface View {
9 | fun openScreen(screenId: String, htmlPage: String)
10 |
11 | fun openLink(url: String)
12 |
13 | fun openDeepLink(url: String)
14 |
15 | fun purchase(productId: String)
16 |
17 | fun restore()
18 |
19 | fun close(actionResult: QActionResult = QActionResult(QActionResultType.Close))
20 |
21 | fun closeAll(actionResult: QActionResult = QActionResult(QActionResultType.Close))
22 |
23 | fun onError(error: QonversionError, shouldCloseScreen: Boolean = false)
24 | }
25 |
26 | internal interface Presenter {
27 | fun confirmScreenView(screenId: String)
28 |
29 | fun shouldOverrideUrlLoading(url: String?): Boolean
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/dto/QAttributionProvider.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.dto
2 |
3 | enum class QAttributionProvider(val id: String) {
4 | AppsFlyer("appsflyer"),
5 | Branch("branch"),
6 | Adjust("adjust")
7 | }
8 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/dto/QEnvironment.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.dto
2 |
3 | import com.qonversion.android.sdk.Qonversion
4 |
5 | /**
6 | * This enum contains different available settings for Environment.
7 | * Provide it to the configuration object via [Qonversion.initialize] while initializing the SDK.
8 | * The default value SDK uses is [QEnvironment.Production].
9 | */
10 | enum class QEnvironment {
11 | Sandbox,
12 | Production
13 | }
14 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/dto/QFallbackObject.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.dto
2 |
3 | import com.qonversion.android.sdk.dto.offerings.QOfferings
4 | import com.qonversion.android.sdk.dto.products.QProduct
5 | import com.squareup.moshi.Json
6 | import com.squareup.moshi.JsonClass
7 |
8 | @JsonClass(generateAdapter = true)
9 | internal data class QFallbackObject(
10 | @Json(name = "products") val products: Map = mapOf(),
11 | @Json(name = "offerings") val offerings: QOfferings?,
12 | @Json(name = "products_permissions") val productPermissions: Map>?,
13 | @Json(name = "remote_config_list") val remoteConfigList: QRemoteConfigList?,
14 | )
15 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/dto/QLaunchMode.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.dto
2 |
3 | import com.qonversion.android.sdk.Qonversion
4 |
5 | /**
6 | * This enum contains an available settings for Qonversion SDK launch mode.
7 | * Provide it to the configuration object via [Qonversion.initialize] while initializing the SDK.
8 | * See more information about launch modes and choose preferred in the documentation
9 | * https://documentation.qonversion.io/docs/how-qonversion-works
10 | */
11 | enum class QLaunchMode {
12 | Analytics, // formerly Observer mode
13 | SubscriptionManagement // formerly Infrastructure mode
14 | }
15 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/dto/QPurchaseModel.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.dto
2 |
3 | import com.qonversion.android.sdk.Qonversion
4 | import com.qonversion.android.sdk.dto.products.QProduct
5 | import com.qonversion.android.sdk.dto.products.QProductStoreDetails
6 |
7 | /**
8 | * Used to provide all the necessary purchase data to the [Qonversion.purchase] method.
9 | * Can be created manually or using the [QProduct.toPurchaseModel] method.
10 | *
11 | * Requires Qonversion product identifier - [productId].
12 | *
13 | * If [offerId] is not specified, then the default offer will be applied. To know how we choose
14 | * the default offer, see [QProductStoreDetails.defaultSubscriptionOfferDetails].
15 | *
16 | * If you want to remove any intro/trial offer from the purchase (use only a bare base plan),
17 | * call the [removeOffer] method.
18 | */
19 | data class QPurchaseModel @JvmOverloads constructor(
20 | val productId: String,
21 | var offerId: String? = null
22 | ) {
23 | internal var applyOffer = true
24 |
25 | fun removeOffer(): QPurchaseModel = apply {
26 | applyOffer = false
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/dto/QPurchaseUpdateModel.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.dto
2 |
3 | import com.qonversion.android.sdk.Qonversion
4 | import com.qonversion.android.sdk.dto.products.QProduct
5 | import com.qonversion.android.sdk.dto.products.QProductStoreDetails
6 |
7 | /**
8 | * Used to provide all the necessary purchase data to the [Qonversion.updatePurchase] method.
9 | * Can be created manually or using the [QProduct.toPurchaseUpdateModel] method.
10 | *
11 | * Requires Qonversion product identifiers - [productId] for the purchasing one and
12 | * [oldProductId] for the purchased one.
13 | *
14 | * If [offerId] is not specified, then the default offer will be applied. To know how we choose
15 | * the default offer, see [QProductStoreDetails.defaultSubscriptionOfferDetails].
16 | *
17 | * If you want to remove any intro/trial offer from the purchase (use only a bare base plan),
18 | * call the [QPurchaseModel.removeOffer] method.
19 | *
20 | * If the [updatePolicy] is not provided, then default one
21 | * will be selected - [QPurchaseUpdatePolicy.WithTimeProration].
22 | */
23 | data class QPurchaseUpdateModel @JvmOverloads constructor(
24 | val productId: String,
25 | var oldProductId: String,
26 | var updatePolicy: QPurchaseUpdatePolicy? = null,
27 | var offerId: String? = null
28 | ) {
29 | internal var applyOffer = true
30 |
31 | fun removeOffer(): QPurchaseUpdateModel = apply {
32 | applyOffer = false
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/dto/QRemoteConfig.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.dto
2 |
3 | import com.qonversion.android.sdk.dto.experiments.QExperiment
4 | import com.squareup.moshi.Json
5 | import com.squareup.moshi.JsonClass
6 |
7 | @JsonClass(generateAdapter = true)
8 | data class QRemoteConfig internal constructor(
9 | @Json(name = "payload") val payload: Map,
10 | @Json(name = "experiment") val experiment: QExperiment?,
11 | @Json(name = "source") internal val sourceApi: QRemoteConfigurationSource?
12 | ) {
13 | val source: QRemoteConfigurationSource get() = sourceApi!!
14 |
15 | internal val isCorrect = sourceApi != null
16 | }
17 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/dto/QRemoteConfigList.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.dto
2 | import com.squareup.moshi.JsonClass
3 |
4 | @JsonClass(generateAdapter = true)
5 | data class QRemoteConfigList internal constructor(
6 | val remoteConfigs: List
7 | ) {
8 | fun remoteConfigForContextKey(key: String): QRemoteConfig? {
9 | return remoteConfigs.find { it.source.contextKey == key }
10 | }
11 |
12 | val remoteConfigForEmptyContextKey: QRemoteConfig? = remoteConfigs.find {
13 | it.source.contextKey == null
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/dto/QRemoteConfigurationAssignmentType.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.dto
2 |
3 | enum class QRemoteConfigurationAssignmentType(val type: String) {
4 | Auto("auto"),
5 | Manual("manual"),
6 | Unknown("unknown");
7 |
8 | companion object {
9 | fun fromType(type: String): QRemoteConfigurationAssignmentType {
10 | return when (type) {
11 | "auto" -> Auto
12 | "manual" -> Manual
13 | else -> Unknown
14 | }
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/dto/QRemoteConfigurationSource.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.dto
2 |
3 | import com.squareup.moshi.Json
4 | import com.squareup.moshi.JsonClass
5 |
6 | @JsonClass(generateAdapter = true)
7 | data class QRemoteConfigurationSource(
8 | @Json(name = "uid") val id: String,
9 | @Json(name = "name") val name: String,
10 | @Json(name = "assignment_type") val assignmentType: QRemoteConfigurationAssignmentType,
11 | @Json(name = "type") val type: QRemoteConfigurationSourceType,
12 | @Json(name = "context_key") internal val contextKeyApi: String?
13 | ) {
14 | val contextKey: String? = contextKeyApi?.takeIf { it.isNotEmpty() }
15 | }
16 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/dto/QRemoteConfigurationSourceType.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.dto
2 |
3 | enum class QRemoteConfigurationSourceType(val type: String) {
4 | ExperimentControlGroup("experiment_control_group"),
5 | ExperimentTreatmentGroup("experiment_treatment_group"),
6 | RemoteConfiguration("remote_configuration"),
7 | Unknown("unknown");
8 |
9 | companion object {
10 | fun fromType(type: String): QRemoteConfigurationSourceType {
11 | return when (type) {
12 | "experiment_control_group" -> ExperimentControlGroup
13 | "experiment_treatment_group" -> ExperimentTreatmentGroup
14 | "remote_configuration" -> RemoteConfiguration
15 | else -> Unknown
16 | }
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/dto/QUser.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.dto
2 |
3 | /**
4 | * This class contains information about the current Qonversion user.
5 | */
6 | data class QUser(
7 | val qonversionId: String,
8 | val identityId: String?
9 | )
10 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/dto/eligibility/QEligibility.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.dto.eligibility
2 |
3 | data class QEligibility(
4 | val status: QIntroEligibilityStatus
5 | )
6 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/dto/eligibility/QIntroEligibilityStatus.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.dto.eligibility
2 |
3 | import com.qonversion.android.sdk.dto.products.QProductType
4 |
5 | enum class QIntroEligibilityStatus(val type: String) {
6 | NonIntroOrTrialProduct("non_intro_or_trial_product"),
7 | Eligible("intro_or_trial_eligible"),
8 | Ineligible("intro_or_trial_ineligible"),
9 | Unknown("unknown");
10 |
11 | companion object {
12 | fun fromType(type: String): QIntroEligibilityStatus {
13 | return when (type) {
14 | "non_intro_or_trial_product" -> NonIntroOrTrialProduct
15 | "intro_or_trial_eligible" -> Eligible
16 | "intro_or_trial_ineligible" -> Ineligible
17 | else -> Unknown
18 | }
19 | }
20 |
21 | fun fromProductType(productType: QProductType): QIntroEligibilityStatus {
22 | return when (productType) {
23 | QProductType.Intro, QProductType.Trial -> Eligible
24 | QProductType.Subscription -> Ineligible
25 | QProductType.InApp -> NonIntroOrTrialProduct
26 | QProductType.Unknown -> Unknown
27 | }
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/dto/entitlements/QEntitlement.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.dto.entitlements
2 |
3 | import com.qonversion.android.sdk.internal.dto.QPermission
4 | import java.util.Date
5 |
6 | data class QEntitlement(
7 | val id: String,
8 | val startedDate: Date,
9 | val expirationDate: Date?,
10 | internal val active: Boolean,
11 | val source: QEntitlementSource,
12 | val productId: String,
13 | val renewState: QEntitlementRenewState,
14 | val renewsCount: Int,
15 | val trialStartDate: Date?,
16 | val firstPurchaseDate: Date?,
17 | val lastPurchaseDate: Date?,
18 | val lastActivatedOfferCode: String?,
19 | val grantType: QEntitlementGrantType,
20 | val autoRenewDisableDate: Date?,
21 | val transactions: List
22 | ) {
23 | internal constructor(permission: QPermission) : this(
24 | permission.permissionID,
25 | permission.startedDate,
26 | permission.expirationDate,
27 | permission.isActive(),
28 | permission.source,
29 | permission.productID,
30 | QEntitlementRenewState.fromProductRenewState(permission.renewState),
31 | permission.renewsCount,
32 | permission.trialStartDate,
33 | permission.firstPurchaseDate,
34 | permission.lastPurchaseDate,
35 | permission.lastActivatedOfferCode,
36 | permission.grantType,
37 | permission.autoRenewDisableDate,
38 | permission.transactions
39 | )
40 |
41 | val isActive get() = active
42 | }
43 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/dto/entitlements/QEntitlementGrantType.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.dto.entitlements
2 |
3 | enum class QEntitlementGrantType(val type: String) {
4 | Purchase("purchase"),
5 | FamilySharing("family_sharing"),
6 | Manual("manual"),
7 | OfferCode("offer_code");
8 |
9 | companion object {
10 | internal fun fromType(type: String): QEntitlementGrantType {
11 | return when (type) {
12 | "purchase" -> Purchase
13 | "family_sharing" -> FamilySharing
14 | "manual" -> Manual
15 | "offerCode" -> OfferCode
16 | else -> Purchase
17 | }
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/dto/entitlements/QEntitlementRenewState.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.dto.entitlements
2 |
3 | import com.qonversion.android.sdk.internal.dto.QProductRenewState
4 |
5 | enum class QEntitlementRenewState(val type: String) {
6 | NonRenewable("non_renewable"),
7 | Unknown("unknown"),
8 | WillRenew("will_renew"),
9 | Canceled("canceled"),
10 | BillingIssue("billing_issue");
11 |
12 | companion object {
13 | internal fun fromType(type: String): QEntitlementRenewState {
14 | return when (type) {
15 | "non_renewable" -> NonRenewable
16 | "will_renew" -> WillRenew
17 | "canceled" -> Canceled
18 | "billing_issue" -> BillingIssue
19 | else -> Unknown
20 | }
21 | }
22 |
23 | internal fun fromProductRenewState(renewState: QProductRenewState): QEntitlementRenewState {
24 | return when (renewState) {
25 | QProductRenewState.NonRenewable -> NonRenewable
26 | QProductRenewState.WillRenew -> WillRenew
27 | QProductRenewState.Canceled -> Canceled
28 | QProductRenewState.BillingIssue -> BillingIssue
29 | else -> Unknown
30 | }
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/dto/entitlements/QEntitlementSource.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.dto.entitlements
2 |
3 | enum class QEntitlementSource(internal val key: String) {
4 | Unknown("unknown"), // Unable to detect the source
5 | AppStore("appstore"), // App Store
6 | PlayStore("playstore"), // Play Store
7 | Stripe("stripe"), // Stripe
8 | Manual("manual"); // The entitlement was activated manually
9 |
10 | companion object {
11 | fun fromKey(key: String): QEntitlementSource {
12 | return values().find { it.key == key } ?: Unknown
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/dto/entitlements/QEntitlementsCacheLifetime.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.dto.entitlements
2 |
3 | enum class QEntitlementsCacheLifetime(val days: Int) {
4 | Week(7),
5 | TwoWeeks(14),
6 | Month(30),
7 | TwoMonths(60),
8 | ThreeMonths(90),
9 | SixMonths(180),
10 | Year(365),
11 | Unlimited(Int.MAX_VALUE)
12 | }
13 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/dto/entitlements/QTransaction.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.dto.entitlements
2 |
3 | import com.squareup.moshi.Json
4 | import com.squareup.moshi.JsonClass
5 | import java.util.Date
6 |
7 | @JsonClass(generateAdapter = true)
8 | data class QTransaction(
9 | @Json(name = "original_transaction_id") val originalTransactionId: String,
10 | @Json(name = "transaction_id") val transactionId: String,
11 | @Json(name = "offer_code") val offerCode: String?,
12 | @Json(name = "transaction_timestamp") val transactionDate: Date,
13 | @Json(name = "expiration_timestamp") val expirationDate: Date?,
14 | @Json(name = "transaction_revoke_timestamp") val transactionRevocationDate: Date?,
15 | @Json(name = "ownership_type") val ownershipType: QTransactionOwnershipType,
16 | @Json(name = "type") val type: QTransactionType,
17 | @Json(name = "environment") val environment: QTransactionEnvironment,
18 | )
19 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/dto/entitlements/QTransactionEnvironment.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.dto.entitlements
2 |
3 | enum class QTransactionEnvironment(val type: String) {
4 | Sandbox("sandbox"),
5 | Production("production");
6 |
7 | companion object {
8 | internal fun fromType(type: String): QTransactionEnvironment {
9 | return when (type) {
10 | "sandbox" -> Sandbox
11 | "production" -> Production
12 | else -> Production
13 | }
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/dto/entitlements/QTransactionOwnershipType.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.dto.entitlements
2 |
3 | enum class QTransactionOwnershipType(val type: String) {
4 | Owner("owner"),
5 | FamilySharing("family_sharing");
6 |
7 | companion object {
8 | internal fun fromType(type: String): QTransactionOwnershipType {
9 | return when (type) {
10 | "owner" -> Owner
11 | "family_sharing" -> FamilySharing
12 | else -> Owner
13 | }
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/dto/entitlements/QTransactionType.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.dto.entitlements
2 |
3 | enum class QTransactionType(val type: String) {
4 | Unknown("unknown"),
5 | SubscriptionStarted("subscription_started"),
6 | SubscriptionRenewed("subscription_renewed"),
7 | TrialStarted("trial_started"),
8 | IntroStarted("intro_started"),
9 | IntroRenewed("intro_renewed"),
10 | NonConsumablePurchase("non_consumable_purchase");
11 |
12 | companion object {
13 | internal fun fromType(type: String): QTransactionType {
14 | return when (type) {
15 | "subscription_started" -> SubscriptionStarted
16 | "subscription_renewed" -> SubscriptionRenewed
17 | "trial_started" -> TrialStarted
18 | "intro_started" -> IntroStarted
19 | "intro_renewed" -> IntroRenewed
20 | "non_consumable_purchase" -> NonConsumablePurchase
21 | else -> Unknown
22 | }
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/dto/experiments/QExperiment.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.dto.experiments
2 |
3 | import com.squareup.moshi.Json
4 | import com.squareup.moshi.JsonClass
5 |
6 | @JsonClass(generateAdapter = true)
7 | data class QExperiment(
8 | @Json(name = "uid") val id: String,
9 | @Json(name = "name") val name: String,
10 | @Json(name = "group") val group: QExperimentGroup
11 | )
12 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/dto/experiments/QExperimentGroup.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.dto.experiments
2 |
3 | import com.squareup.moshi.Json
4 | import com.squareup.moshi.JsonClass
5 |
6 | @JsonClass(generateAdapter = true)
7 | data class QExperimentGroup(
8 | @Json(name = "uid") val id: String,
9 | @Json(name = "name") val name: String,
10 | @Json(name = "type") val type: QExperimentGroupType
11 | )
12 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/dto/experiments/QExperimentGroupType.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.dto.experiments
2 |
3 | enum class QExperimentGroupType(val type: String) {
4 | Control("control"),
5 | Treatment("treatment"),
6 | Unknown("unknown");
7 |
8 | companion object {
9 | fun fromType(type: String): QExperimentGroupType {
10 | return when (type) {
11 | "control" -> Control
12 | "treatment" -> Treatment
13 | else -> Unknown
14 | }
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/dto/offerings/QOffering.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.dto.offerings
2 |
3 | import com.qonversion.android.sdk.dto.products.QProduct
4 | import com.qonversion.android.sdk.internal.equalsIgnoreOrder
5 | import com.squareup.moshi.Json
6 | import com.squareup.moshi.JsonClass
7 |
8 | @JsonClass(generateAdapter = true)
9 | class QOffering(
10 | @Json(name = "id") val offeringID: String,
11 | @Json(name = "tag") val tag: QOfferingTag,
12 | @Json(name = "products") val products: List = listOf()
13 | ) {
14 |
15 | fun productForID(id: String): QProduct? {
16 | return products.firstOrNull { it.qonversionID == id }
17 | }
18 |
19 | override fun hashCode(): Int {
20 | return offeringID.hashCode()
21 | }
22 |
23 | override fun equals(other: Any?): Boolean {
24 | return other is QOffering &&
25 | other.offeringID == offeringID &&
26 | other.tag == tag &&
27 | other.products equalsIgnoreOrder products
28 | }
29 |
30 | override fun toString(): String {
31 | return "QOffering(offeringID=$offeringID, tag=$tag, products=$products)"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/dto/offerings/QOfferingTag.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.dto.offerings
2 |
3 | enum class QOfferingTag(val tag: Int?) {
4 | Unknown(-1),
5 | None(0),
6 | Main(1);
7 |
8 | companion object {
9 | fun fromTag(tag: Int?): QOfferingTag {
10 | return when (tag ?: 0) {
11 | 0 -> None
12 | 1 -> Main
13 | else -> Unknown
14 | }
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/dto/offerings/QOfferings.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.dto.offerings
2 |
3 | import com.qonversion.android.sdk.internal.equalsIgnoreOrder
4 |
5 | data class QOfferings(
6 | val main: QOffering?,
7 | val availableOfferings: List = listOf()
8 | ) {
9 | fun offeringForID(id: String): QOffering? {
10 | return availableOfferings.firstOrNull { it.offeringID == id }
11 | }
12 |
13 | override fun hashCode(): Int {
14 | return super.hashCode()
15 | }
16 |
17 | override fun equals(other: Any?): Boolean {
18 | return other is QOfferings &&
19 | (other === this ||
20 | main == other.main &&
21 | availableOfferings equalsIgnoreOrder other.availableOfferings)
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/dto/products/QProductInAppDetails.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.dto.products
2 |
3 | import com.android.billingclient.api.ProductDetails.OneTimePurchaseOfferDetails
4 |
5 | /**
6 | * This class contains all the information about the Google in-app product details.
7 | */
8 | data class QProductInAppDetails(
9 | /**
10 | * Original [OneTimePurchaseOfferDetails] received from Google Play Billing Library.
11 | */
12 | val originalOneTimePurchaseOfferDetails: OneTimePurchaseOfferDetails
13 | ) {
14 | /**
15 | * The price of the in-app product.
16 | */
17 | val price: QProductPrice = originalOneTimePurchaseOfferDetails.let {
18 | QProductPrice(it.priceAmountMicros, it.priceCurrencyCode, it.formattedPrice)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/dto/products/QProductInstallmentPlanDetails.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.dto.products
2 |
3 | import com.android.billingclient.api.ProductDetails.InstallmentPlanDetails
4 |
5 | /**
6 | * This class represents the details about the installment plan for a subscription product.
7 | */
8 | data class QProductInstallmentPlanDetails(
9 | /**
10 | * Original [InstallmentPlanDetails] received from Google Play Billing Library
11 | */
12 | val originalInstallmentPlanDetails: InstallmentPlanDetails
13 | ) {
14 | /**
15 | * Committed payments count after a user signs up for this subscription plan.
16 | */
17 | val commitmentPaymentsCount: Int =
18 | originalInstallmentPlanDetails.installmentPlanCommitmentPaymentsCount
19 |
20 | /**
21 | * Subsequent committed payments count after this subscription plan renews.
22 | *
23 | * Returns 0 if the installment plan doesn't have any subsequent commitment,
24 | * which means this subscription plan will fall back to a normal
25 | * non-installment monthly plan when the plan renews.
26 | */
27 | val subsequentCommitmentPaymentsCount: Int =
28 | originalInstallmentPlanDetails.subsequentInstallmentPlanCommitmentPaymentsCount
29 | }
30 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/dto/products/QProductPrice.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.dto.products
2 |
3 | import java.util.Currency
4 |
5 | /**
6 | * Information about the product's price.
7 | */
8 | data class QProductPrice(
9 | /**
10 | * Total amount of money in micro-units,
11 | * where 1,000,000 micro-units equal one unit of the currency.
12 | */
13 | val priceAmountMicros: Long,
14 |
15 | /**
16 | * ISO 4217 currency code for price.
17 | */
18 | val priceCurrencyCode: String,
19 |
20 | /**
21 | * Formatted price for the payment, including its currency sign.
22 | */
23 | val formattedPrice: String,
24 | ) {
25 | /**
26 | * True, if the price is zero. False otherwise.
27 | */
28 | val isFree: Boolean = priceAmountMicros == 0L
29 |
30 | /**
31 | * [Currency] object from the [priceCurrencyCode]. Null if failed to parse.
32 | */
33 | val currency: Currency? = try {
34 | Currency.getInstance(priceCurrencyCode)
35 | } catch (_: IllegalArgumentException) {
36 | null
37 | }
38 |
39 | /**
40 | * Price currency symbol. Null if failed to parse.
41 | */
42 | val currencySymbol: String? = currency?.symbol
43 |
44 | /**
45 | * Price amount in currency units
46 | */
47 | val priceAmount = priceAmountMicros / 1_000_000.0
48 | }
49 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/dto/products/QProductType.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.dto.products
2 |
3 | enum class QProductType {
4 | Unknown,
5 | Trial,
6 | Intro,
7 | Subscription,
8 | InApp;
9 |
10 | companion object {
11 | fun fromType(type: Int): QProductType {
12 | return when (type) {
13 | 0 -> Trial
14 | 1 -> Subscription
15 | 2 -> InApp
16 | else -> throw IllegalArgumentException("Undefined enum type")
17 | }
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/dto/properties/QUserProperty.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.dto.properties
2 |
3 | import com.squareup.moshi.Json
4 | import com.squareup.moshi.JsonClass
5 |
6 | @JsonClass(generateAdapter = true)
7 | data class QUserProperty(
8 | @Json(name = "key") val key: String,
9 | @Json(name = "value") val value: String
10 | ) {
11 | /**
12 | * [QUserPropertyKey] used to set this property.
13 | * Returns [QUserPropertyKey.Custom] for custom properties.
14 | */
15 | val definedKey: QUserPropertyKey = QUserPropertyKey.fromString(key)
16 | }
17 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/dto/properties/QUserPropertyKey.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.dto.properties
2 |
3 | enum class QUserPropertyKey(val userPropertyCode: String) {
4 | Email("_q_email"),
5 | Name("_q_name"),
6 | KochavaDeviceId("_q_kochava_device_id"),
7 | AppsFlyerUserId("_q_appsflyer_user_id"),
8 | AdjustAdId("_q_adjust_adid"),
9 | CustomUserId("_q_custom_user_id"),
10 | FacebookAttribution("_q_fb_attribution"),
11 | FirebaseAppInstanceId("_q_firebase_instance_id"),
12 | AppSetId("_q_app_set_id"),
13 | AdvertisingId("_q_advertising_id"), // iOS only
14 | AppMetricaDeviceId("_q_appmetrica_device_id"),
15 | AppMetricaUserProfileId("_q_appmetrica_user_profile_id"),
16 | PushWooshHwId("_q_pushwoosh_hwid"),
17 | PushWooshUserId("_q_pushwoosh_user_id"),
18 | TenjinAnalyticsInstallationId("_q_tenjin_aiid"),
19 | Custom("");
20 |
21 | companion object {
22 | internal fun fromString(key: String) =
23 | values().find { it.userPropertyCode == key } ?: Custom
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/AppLifeCycleHandler.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal
2 |
3 | import androidx.lifecycle.DefaultLifecycleObserver
4 | import androidx.lifecycle.LifecycleOwner
5 |
6 | internal class AppLifecycleHandler(private val lifecycleDelegate: LifecycleDelegate) :
7 | DefaultLifecycleObserver {
8 |
9 | override fun onStart(owner: LifecycleOwner) {
10 | lifecycleDelegate.onAppForeground()
11 | }
12 |
13 | override fun onStop(owner: LifecycleOwner) {
14 | lifecycleDelegate.onAppBackground()
15 | }
16 | }
17 |
18 | internal interface LifecycleDelegate {
19 | fun onAppBackground()
20 | fun onAppForeground()
21 | }
22 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/AppState.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal
2 |
3 | internal enum class AppState {
4 | Foreground,
5 | Background;
6 |
7 | fun isBackground() =
8 | this == Background
9 | }
10 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/Constants.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal
2 |
3 | internal object Constants {
4 | const val PREFS_PREFIX = "com.qonversion.keys"
5 | const val PREFS_ORIGINAL_USER_ID_KEY = "$PREFS_PREFIX.originalUserID"
6 | const val PREFS_QONVERSION_USER_ID_KEY = "$PREFS_PREFIX.storedUserID"
7 | const val PREFS_PARTNER_IDENTITY_ID_KEY = "$PREFS_PREFIX.partnerIdentityUserID"
8 | const val USER_ID_PREFIX = "QON"
9 | const val USER_ID_SEPARATOR = "_"
10 | const val IS_HISTORICAL_DATA_SYNCED = "$PREFS_PREFIX.is_historical_data_synced"
11 | const val INTERNAL_SERVER_ERROR_MIN = 500
12 | const val INTERNAL_SERVER_ERROR_MAX = 599
13 | const val PRICE_MICROS_DIVIDER: Double = 1000000.0
14 | const val CRASH_LOGS_URL = "https://sdk-logs.qonversion.io/sdk.log"
15 | const val CRASH_LOG_FILE_SUFFIX = ".qonversion.stacktrace"
16 | }
17 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/HttpError.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal
2 |
3 | internal data class HttpError(
4 | val code: Int,
5 | val message: String
6 | )
7 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/IncrementalDelayCalculator.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal
2 |
3 | import java.util.*
4 | import kotlin.math.min
5 | import kotlin.math.pow
6 | import kotlin.math.roundToInt
7 |
8 | internal class IncrementalDelayCalculator(private val randomizer: Random) {
9 |
10 | companion object {
11 | private const val JITTER = 0.4f
12 | private const val FACTOR = 2.4f
13 | private const val MAX_DELAY = 1000
14 | }
15 |
16 | @Throws(IllegalArgumentException::class)
17 | fun countDelay(minDelay: Int, retriesCount: Int): Int {
18 | var delay = minDelay + FACTOR.pow(retriesCount)
19 | var delta = (delay * JITTER).roundToInt()
20 |
21 | if (delta != Int.MAX_VALUE) {
22 | delta += 1
23 | }
24 |
25 | delay += randomizer.nextInt(delta)
26 | val resultDelay = min(delay.roundToInt(), MAX_DELAY)
27 |
28 | return resultDelay
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/LoadStoreProductsState.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal
2 |
3 | internal enum class LoadStoreProductsState {
4 | NotStartedYet,
5 | Loading,
6 | Loaded,
7 | Failed
8 | }
9 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/QAttributionManager.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal
2 |
3 | import com.qonversion.android.sdk.dto.QAttributionProvider
4 | import com.qonversion.android.sdk.internal.provider.AppStateProvider
5 | import com.qonversion.android.sdk.internal.repository.QRepository
6 |
7 | internal class QAttributionManager internal constructor(
8 | private val repository: QRepository,
9 | private val appStateProvider: AppStateProvider
10 | ) {
11 | private var pendingAttributionProvider: QAttributionProvider? = null
12 | private var pendingData: Map? = null
13 |
14 | fun onAppForeground() {
15 | val source = pendingAttributionProvider
16 | val info = pendingData
17 | if (source != null && !info.isNullOrEmpty()) {
18 | repository.attribution(info, source.id)
19 |
20 | pendingData = null
21 | pendingAttributionProvider = null
22 | }
23 | }
24 |
25 | fun attribution(data: Map, provider: QAttributionProvider) {
26 | if (appStateProvider.appState.isBackground()) {
27 | pendingAttributionProvider = provider
28 | pendingData = data
29 | return
30 | }
31 |
32 | repository.attribution(data, provider.id)
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/QHandledPurchasesCache.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal
2 |
3 | import com.android.billingclient.api.Purchase
4 | import javax.inject.Inject
5 |
6 | internal class QHandledPurchasesCache @Inject internal constructor() {
7 | private val handledOrderIDs = mutableSetOf()
8 |
9 | fun shouldHandlePurchase(purchase: Purchase): Boolean {
10 | return !handledOrderIDs.contains(purchase.orderId)
11 | }
12 |
13 | fun saveHandledPurchase(purchase: Purchase) {
14 | purchase.orderId?.let {
15 | handledOrderIDs.add(it)
16 | }
17 | }
18 |
19 | fun saveHandledPurchases(purchases: Collection) {
20 | handledOrderIDs.addAll(purchases.mapNotNull { it.orderId })
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/QIdentityManager.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal
2 |
3 | import com.qonversion.android.sdk.dto.QonversionError
4 | import com.qonversion.android.sdk.internal.repository.QRepository
5 | import com.qonversion.android.sdk.internal.services.QUserInfoService
6 | import javax.inject.Inject
7 |
8 | interface IdentityManagerCallback {
9 | fun onSuccess(qonversionUid: String)
10 | fun onError(error: QonversionError)
11 | }
12 |
13 | internal class QIdentityManager @Inject constructor(
14 | private val repository: QRepository,
15 | private val userInfoService: QUserInfoService
16 | ) {
17 | val currentPartnersIdentityId: String? get() = userInfoService.getPartnersIdentityId()
18 |
19 | fun identify(userID: String, callback: IdentityManagerCallback) {
20 | val currentUserID = userInfoService.obtainUserID()
21 | repository.identify(userID, currentUserID,
22 | onSuccess = { resultUserID ->
23 | userInfoService.storePartnersIdentityId(userID)
24 | if (resultUserID.isNotEmpty()) {
25 | userInfoService.storeQonversionUserId(resultUserID)
26 | }
27 |
28 | callback.onSuccess(resultUserID)
29 | },
30 | onError = {
31 | callback.onError(it)
32 | })
33 | }
34 |
35 | fun logoutIfNeeded(): Boolean {
36 | return userInfoService.logoutIfNeeded()
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/api/ApiHelper.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.api
2 |
3 | import okhttp3.Request
4 |
5 | internal class ApiHelper(apiUrl: String) {
6 |
7 | private val v0MethodsRegex = "$apiUrl(?!v\\d+/).*"
8 | private val v1MethodsRegex = "${apiUrl}v1/.*"
9 |
10 | fun isDeprecatedEndpoint(request: Request) = isV0Request(request) || isV1Request(request)
11 |
12 | fun isV0Request(request: Request) = checkRequestVersion(request, v0MethodsRegex)
13 |
14 | fun isV1Request(request: Request) = checkRequestVersion(request, v1MethodsRegex)
15 |
16 | private fun checkRequestVersion(request: Request, regexStr: String): Boolean {
17 | val regex = Regex(regexStr)
18 | val results = regex.findAll(request.url().toString(), 0)
19 |
20 | return results.any()
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/api/NetworkInterceptor.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.api
2 |
3 | import com.qonversion.android.sdk.internal.HttpError
4 | import com.qonversion.android.sdk.internal.InternalConfig
5 | import okhttp3.Interceptor
6 | import okhttp3.Protocol
7 | import okhttp3.Response
8 | import okhttp3.ResponseBody
9 | import java.io.IOException
10 | import javax.inject.Inject
11 |
12 | internal class NetworkInterceptor @Inject constructor(
13 | private val headersProvider: ApiHeadersProvider,
14 | private val apiHelper: ApiHelper,
15 | private val config: InternalConfig
16 | ) : Interceptor {
17 |
18 | @Throws(IOException::class)
19 | override fun intercept(chain: Interceptor.Chain): Response {
20 | val fatalError = config.fatalError
21 |
22 | return if (fatalError != null) {
23 | Response.Builder()
24 | .code(fatalError.code)
25 | .body(ResponseBody.create(null, ""))
26 | .protocol(Protocol.HTTP_2)
27 | .message(fatalError.message)
28 | .request(chain.request())
29 | .build()
30 | } else {
31 | var request = chain.request()
32 | request = request.newBuilder()
33 | .headers(headersProvider.getHeaders(request.headers()))
34 | .build()
35 |
36 | val response = chain.proceed(request)
37 | if (response.code() in FATAL_ERRORS && apiHelper.isDeprecatedEndpoint(request)) {
38 | config.fatalError = HttpError(response.code(), response.message())
39 | }
40 |
41 | return response
42 | }
43 | }
44 |
45 | companion object {
46 | private val FATAL_ERRORS = listOf(401, 402, 403)
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/api/RateLimiter.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.api
2 |
3 | import com.qonversion.android.sdk.internal.toInt
4 |
5 | private const val MS_IN_SEC = 1000
6 |
7 | internal class RateLimiter(private val maxRequestsPerSecond: Int) {
8 | private val requests = mutableMapOf>()
9 |
10 | @Synchronized
11 | fun saveRequest(requestType: RequestType, hash: Int) {
12 | val ts = System.currentTimeMillis()
13 |
14 | if (!requests.containsKey(requestType)) {
15 | requests[requestType] = mutableListOf()
16 | }
17 |
18 | val request = Request(hash, ts)
19 | requests[requestType]?.add(request)
20 | }
21 |
22 | @Synchronized
23 | fun isRateLimitExceeded(requestType: RequestType, hash: Int): Boolean {
24 | removeOutdatedRequests(requestType)
25 |
26 | val requestsPerType = requests[requestType] ?: emptyList()
27 |
28 | var matchCount = 0
29 | for (request in requestsPerType) {
30 | if (matchCount >= maxRequestsPerSecond) {
31 | break
32 | }
33 |
34 | matchCount += (request.hash == hash).toInt()
35 | }
36 |
37 | return matchCount >= maxRequestsPerSecond
38 | }
39 |
40 | @Synchronized
41 | private fun removeOutdatedRequests(requestType: RequestType) {
42 | val ts = System.currentTimeMillis()
43 | val requestsPerType = requests[requestType] ?: emptyList()
44 | requests[requestType] = requestsPerType
45 | .filter { ts - it.timestamp < MS_IN_SEC }
46 | .toMutableList()
47 | }
48 |
49 | private class Request(
50 | val hash: Int,
51 | val timestamp: Long
52 | )
53 | }
54 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/api/RequestTrigger.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.api
2 |
3 | // Represents the initial trigger for the API request
4 | internal enum class RequestTrigger(val key: String) {
5 | Init("Init"),
6 | Identify("Identify"),
7 | Products("Products"),
8 | Purchase("Purchase"),
9 | UserProperties("UserProperties"),
10 | Restore("Restore"),
11 | SyncHistoricalData("SyncHistoricalData"),
12 | SyncPurchases("SyncPurchases"),
13 | ActualizePermissions("ActualizePermissions"),
14 | Logout("Logout"),
15 | }
16 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/api/RequestType.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.api
2 |
3 | internal enum class RequestType {
4 | Init,
5 | RemoteConfig,
6 | RemoteConfigList,
7 | AttachUserToExperiment,
8 | DetachUserFromExperiment,
9 | Purchase,
10 | Restore,
11 | Attribution,
12 | GetProperties,
13 | EligibilityForProductIds,
14 | Identify,
15 | AttachUserToRemoteConfiguration,
16 | DetachUserFromRemoteConfiguration,
17 | }
18 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/billing/BillingError.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.billing
2 |
3 | import com.android.billingclient.api.BillingClient
4 |
5 | internal data class BillingError(
6 | @BillingClient.BillingResponseCode val billingResponseCode: Int,
7 | val message: String
8 | )
9 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/billing/BillingService.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.billing
2 |
3 | import android.app.Activity
4 | import com.android.billingclient.api.Purchase
5 | import com.qonversion.android.sdk.dto.products.QProduct
6 | import com.qonversion.android.sdk.internal.dto.QStoreProductType
7 | import com.qonversion.android.sdk.internal.dto.purchase.PurchaseModelInternalEnriched
8 | import com.qonversion.android.sdk.internal.purchase.PurchaseHistory
9 |
10 | internal interface BillingService {
11 |
12 | fun enrichStoreDataAsync(
13 | products: List,
14 | onFailed: (error: BillingError) -> Unit,
15 | onEnriched: (products: List) -> Unit
16 | )
17 |
18 | fun enrichStoreData(products: List)
19 |
20 | fun purchase(
21 | activity: Activity,
22 | purchaseModel: PurchaseModelInternalEnriched,
23 | )
24 |
25 | fun consumePurchases(purchases: List)
26 |
27 | fun consumeHistoryRecords(historyRecords: List)
28 |
29 | fun queryPurchasesHistory(
30 | onFailed: (error: BillingError) -> Unit,
31 | onCompleted: (purchases: List) -> Unit
32 | )
33 |
34 | fun queryPurchases(
35 | onFailed: (error: BillingError) -> Unit,
36 | onCompleted: (purchases: List) -> Unit
37 | )
38 |
39 | fun getStoreProductType(
40 | storeId: String,
41 | onFailed: (error: BillingError) -> Unit,
42 | onSuccess: (type: QStoreProductType) -> Unit
43 | )
44 | }
45 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/billing/UpdatePurchaseInfo.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.billing
2 |
3 | import com.qonversion.android.sdk.dto.QPurchaseUpdatePolicy
4 |
5 | internal data class UpdatePurchaseInfo(
6 | val purchaseToken: String,
7 | val updatePolicy: QPurchaseUpdatePolicy? = null
8 | )
9 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/converter/GooglePurchaseConverter.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.converter
2 |
3 | import com.qonversion.android.sdk.dto.QPurchaseOptions
4 | import com.qonversion.android.sdk.internal.billing.productId
5 | import com.qonversion.android.sdk.internal.milliSecondsToSeconds
6 | import com.qonversion.android.sdk.internal.purchase.Purchase
7 |
8 | internal class GooglePurchaseConverter : PurchaseConverter {
9 |
10 | override fun convertPurchases(
11 | purchases: List,
12 | options: Map?
13 | ): List {
14 | return purchases.map { convertPurchase(it, options?.get(it.productId)) }
15 | }
16 |
17 | override fun convertPurchase(purchase: com.android.billingclient.api.Purchase, options: QPurchaseOptions?): Purchase {
18 | return Purchase(
19 | storeProductId = purchase.productId,
20 | orderId = purchase.orderId ?: "",
21 | originalOrderId = formatOriginalTransactionId(purchase.orderId ?: ""),
22 | purchaseTime = purchase.purchaseTime.milliSecondsToSeconds(),
23 | purchaseToken = purchase.purchaseToken,
24 | contextKeys = options?.contextKeys,
25 | screenUid = options?.screenUid,
26 | )
27 | }
28 |
29 | private fun formatOriginalTransactionId(transactionId: String): String {
30 | val regex = Regex("\\.{2}.*")
31 |
32 | return regex.replace(transactionId, "")
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/converter/PurchaseConverter.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.converter
2 |
3 | import com.qonversion.android.sdk.dto.QPurchaseOptions
4 | import com.qonversion.android.sdk.internal.purchase.Purchase
5 |
6 | internal interface PurchaseConverter {
7 | fun convertPurchase(purchase: com.android.billingclient.api.Purchase, options: QPurchaseOptions?): Purchase
8 |
9 | fun convertPurchases(
10 | purchases: List,
11 | options: Map?
12 | ): List
13 | }
14 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/di/QDependencyInjector.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.di
2 |
3 | import android.app.Application
4 | import com.qonversion.android.sdk.internal.InternalConfig
5 | import com.qonversion.android.sdk.internal.di.component.AppComponent
6 | import com.qonversion.android.sdk.internal.di.component.DaggerAppComponent
7 | import com.qonversion.android.sdk.internal.di.module.AppModule
8 | import com.qonversion.android.sdk.internal.di.module.ManagersModule
9 | import com.qonversion.android.sdk.internal.di.module.RepositoryModule
10 | import com.qonversion.android.sdk.internal.provider.AppStateProvider
11 |
12 | internal object QDependencyInjector {
13 | internal lateinit var appComponent: AppComponent
14 | private set
15 |
16 | internal fun buildAppComponent(
17 | context: Application,
18 | internalConfig: InternalConfig,
19 | appStateProvider: AppStateProvider
20 | ): AppComponent {
21 | appComponent = DaggerAppComponent
22 | .builder()
23 | .appModule(AppModule(context, internalConfig, appStateProvider))
24 | .repositoryModule(RepositoryModule())
25 | .managersModule(ManagersModule())
26 | .build()
27 |
28 | return appComponent
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/di/component/FragmentComponent.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.di.component
2 |
3 | import com.qonversion.android.sdk.internal.di.module.FragmentModule
4 | import com.qonversion.android.sdk.internal.di.scope.ActivityScope
5 | import com.qonversion.android.sdk.automations.mvp.ScreenFragment
6 | import dagger.Component
7 |
8 | @ActivityScope
9 | @Component(dependencies = [AppComponent::class], modules = [FragmentModule::class])
10 | internal interface FragmentComponent {
11 | fun inject(into: ScreenFragment)
12 | }
13 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/di/module/FragmentModule.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.di.module
2 |
3 | import com.qonversion.android.sdk.automations.internal.macros.ScreenProcessor
4 | import com.qonversion.android.sdk.internal.di.scope.ActivityScope
5 | import com.qonversion.android.sdk.automations.mvp.ScreenContract
6 | import dagger.Module
7 | import dagger.Provides
8 |
9 | @Module
10 | internal class FragmentModule(private val view: ScreenContract.View) {
11 |
12 | @ActivityScope
13 | @Provides
14 | fun provideScreenView(): ScreenContract.View {
15 | return view
16 | }
17 |
18 | @ActivityScope
19 | @Provides
20 | fun provideScreenProcessor(): ScreenProcessor {
21 | return ScreenProcessor()
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/di/module/ServicesModule.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.di.module
2 |
3 | import com.qonversion.android.sdk.internal.di.scope.ApplicationScope
4 | import com.qonversion.android.sdk.internal.services.QUserInfoService
5 | import com.qonversion.android.sdk.internal.storage.SharedPreferencesCache
6 | import com.qonversion.android.sdk.internal.storage.TokenStorage
7 | import dagger.Module
8 | import dagger.Provides
9 |
10 | @Module
11 | internal class ServicesModule {
12 |
13 | @ApplicationScope
14 | @Provides
15 | fun provideUserInfoService(
16 | cacheStorage: SharedPreferencesCache,
17 | tokenStorage: TokenStorage
18 | ): QUserInfoService {
19 | return QUserInfoService(cacheStorage, tokenStorage)
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/di/scope/ActivityScope.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.di.scope
2 |
3 | import javax.inject.Scope
4 |
5 | @Scope
6 | @kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
7 | internal annotation class ActivityScope
8 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/di/scope/ApplicationScope.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.di.scope
2 |
3 | import javax.inject.Scope
4 |
5 | @Scope
6 | @kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
7 | internal annotation class ApplicationScope
8 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/ActionPoints.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.dto
2 |
3 | import com.qonversion.android.sdk.internal.dto.automations.ActionPointScreen
4 | import com.squareup.moshi.Json
5 | import com.squareup.moshi.JsonClass
6 |
7 | @JsonClass(generateAdapter = true)
8 | internal data class ActionPoints(
9 | @Json(name = "items") val items: List>
10 | )
11 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/BaseResponse.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.dto
2 |
3 | import com.squareup.moshi.Json
4 | import com.squareup.moshi.JsonClass
5 |
6 | @JsonClass(generateAdapter = true)
7 | internal data class BaseResponse(
8 | @Json(name = "success") val success: Boolean,
9 | @Json(name = "data") val data: T
10 | )
11 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/Data.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.dto
2 |
3 | import com.squareup.moshi.Json
4 | import com.squareup.moshi.JsonClass
5 |
6 | @JsonClass(generateAdapter = true)
7 | internal data class Data(
8 | @Json(name = "data") val data: T
9 | )
10 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/Environment.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.dto
2 |
3 | import com.squareup.moshi.Json
4 | import com.squareup.moshi.JsonClass
5 | @JsonClass(generateAdapter = true)
6 | internal data class Environment(
7 | @Json(name = "app_version") val app_version: String,
8 | @Json(name = "carrier") val carrier: String,
9 | @Json(name = "device_id") val deviceId: String,
10 | @Json(name = "locale") val locale: String,
11 | @Json(name = "manufacturer") val manufacturer: String,
12 | @Json(name = "model") val model: String,
13 | @Json(name = "os") val os: String,
14 | @Json(name = "os_version") val osVersion: String,
15 | @Json(name = "timezone") val timezone: String,
16 | @Json(name = "platform") val platform: String,
17 | @Json(name = "country") val country: String,
18 | @Json(name = "advertiser_id") val advertiserId: String?
19 | )
20 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/ProductStoreId.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.dto
2 |
3 | internal data class ProductStoreId(
4 | val productId: String,
5 | val basePlanId: String?, // absent for inapp products
6 | val offerId: String? = null
7 | )
8 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/ProviderData.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.dto
2 |
3 | import com.squareup.moshi.Json
4 | import com.squareup.moshi.JsonClass
5 |
6 | @JsonClass(generateAdapter = true)
7 | internal data class ProviderData(
8 | @Json(name = "d") val data: Map,
9 | @Json(name = "provider") val provider: String
10 | )
11 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/QLaunchResult.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.dto
2 |
3 | import com.qonversion.android.sdk.dto.offerings.QOfferings
4 | import com.qonversion.android.sdk.dto.products.QProduct
5 | import com.squareup.moshi.Json
6 | import com.squareup.moshi.JsonClass
7 | import java.util.Date
8 |
9 | @JsonClass(generateAdapter = true)
10 | internal data class QLaunchResult internal constructor(
11 | @Json(name = "uid") val uid: String,
12 | @Json(name = "timestamp") val date: Date,
13 | @Json(name = "products") val products: Map = mapOf(),
14 | @Json(name = "permissions") internal var permissions: Map = mapOf(),
15 | @Json(name = "user_products") val userProducts: Map = mapOf(),
16 | @Json(name = "offerings") val offerings: QOfferings?,
17 | @Json(name = "products_permissions") internal val productPermissions: Map>? = null
18 | )
19 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/QPermission.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.dto
2 |
3 | import com.qonversion.android.sdk.dto.entitlements.QEntitlementGrantType
4 | import com.qonversion.android.sdk.dto.entitlements.QEntitlementSource
5 | import com.qonversion.android.sdk.dto.entitlements.QTransaction
6 | import com.qonversion.android.sdk.internal.toBoolean
7 | import com.squareup.moshi.Json
8 | import com.squareup.moshi.JsonClass
9 | import java.util.Date
10 |
11 | @JsonClass(generateAdapter = true)
12 | internal data class QPermission(
13 | @Json(name = "id") val permissionID: String,
14 | @Json(name = "associated_product") val productID: String,
15 | @Json(name = "renew_state") val renewState: QProductRenewState,
16 | @Json(name = "started_timestamp") val startedDate: Date,
17 | @Json(name = "expiration_timestamp") val expirationDate: Date?,
18 | @Json(name = "source") val source: QEntitlementSource = QEntitlementSource.Unknown,
19 | @Json(name = "active") internal val active: Int,
20 | @Json(name = "renews_count") val renewsCount: Int = 0,
21 | @Json(name = "trial_start_timestamp") val trialStartDate: Date?,
22 | @Json(name = "first_purchase_timestamp") val firstPurchaseDate: Date?,
23 | @Json(name = "last_purchase_timestamp") val lastPurchaseDate: Date?,
24 | @Json(name = "last_activated_offer_code") val lastActivatedOfferCode: String?,
25 | @Json(name = "grant_type") val grantType: QEntitlementGrantType = QEntitlementGrantType.Purchase,
26 | @Json(name = "auto_renew_disable_timestamp") val autoRenewDisableDate: Date?,
27 | @Json(name = "store_transactions") val transactions: List = emptyList()
28 | ) {
29 | fun isActive(): Boolean {
30 | return active.toBoolean()
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/QProductRenewState.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.dto
2 |
3 | internal enum class QProductRenewState(val type: Int) {
4 | NonRenewable(-1),
5 | Unknown(0),
6 | WillRenew(1),
7 | Canceled(2),
8 | BillingIssue(3);
9 |
10 | companion object {
11 | fun fromType(type: Int): QProductRenewState {
12 | return when (type) {
13 | -1 -> NonRenewable
14 | 1 -> WillRenew
15 | 2 -> Canceled
16 | 3 -> BillingIssue
17 | else -> Unknown
18 | }
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/QStoreProductType.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.dto
2 |
3 | import com.android.billingclient.api.BillingClient
4 |
5 | internal enum class QStoreProductType {
6 | InApp,
7 | Subscription;
8 |
9 | @BillingClient.ProductType
10 | fun toProductType(): String {
11 | return when (this) {
12 | InApp -> BillingClient.ProductType.INAPP
13 | Subscription -> BillingClient.ProductType.SUBS
14 | }
15 | }
16 |
17 | @Suppress("DEPRECATION")
18 | @BillingClient.SkuType
19 | fun toSkuType(): String {
20 | return when (this) {
21 | InApp -> BillingClient.SkuType.INAPP
22 | Subscription -> BillingClient.SkuType.SUBS
23 | }
24 | }
25 |
26 | companion object {
27 | fun fromProductType(@BillingClient.ProductType type: String): QStoreProductType {
28 | return if (type == BillingClient.ProductType.INAPP) {
29 | InApp
30 | } else {
31 | Subscription
32 | }
33 | }
34 |
35 | @Suppress("DEPRECATION")
36 | fun fromSkuType(@BillingClient.SkuType type: String): QStoreProductType {
37 | return if (type == BillingClient.SkuType.INAPP) {
38 | InApp
39 | } else {
40 | Subscription
41 | }
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/Response.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.dto
2 |
3 | import com.squareup.moshi.Json
4 | import com.squareup.moshi.JsonClass
5 |
6 | @JsonClass(generateAdapter = true)
7 | internal data class Response(
8 | @Json(name = "client_id") val clientId: String?,
9 | @Json(name = "client_uid") val clientUid: String?,
10 | @Json(name = "client_target_id") val clientTargetId: String?
11 | )
12 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/SendPropertiesResult.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.dto
2 |
3 | import com.qonversion.android.sdk.dto.properties.QUserProperty
4 | import com.squareup.moshi.Json
5 | import com.squareup.moshi.JsonClass
6 |
7 | @JsonClass(generateAdapter = true)
8 | internal data class SendPropertiesResult(
9 | @Json(name = "savedProperties") val savedProperties: List,
10 | @Json(name = "propertyErrors") val propertyErrors: List,
11 | ) {
12 | @JsonClass(generateAdapter = true)
13 | internal data class PropertyError(
14 | @Json(name = "key") val key: String,
15 | @Json(name = "error") val error: String
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/app/App.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.dto.app
2 |
3 | import com.squareup.moshi.Json
4 | import com.squareup.moshi.JsonClass
5 |
6 | @JsonClass(generateAdapter = true)
7 | internal data class App(
8 | @Json(name = "name") val name: String,
9 | @Json(name = "version") val version: String,
10 | @Json(name = "build") val build: String,
11 | @Json(name = "bundle") val bundle: String
12 | )
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/automations/ActionPointScreen.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.dto.automations
2 |
3 | import com.squareup.moshi.Json
4 | import com.squareup.moshi.JsonClass
5 |
6 | @JsonClass(generateAdapter = true)
7 | internal data class ActionPointScreen(
8 | @Json(name = "screen") val screenId: String
9 | )
10 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/automations/Screen.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.dto.automations
2 |
3 | import com.squareup.moshi.Json
4 | import com.squareup.moshi.JsonClass
5 |
6 | @JsonClass(generateAdapter = true)
7 | internal data class Screen(
8 | @Json(name = "id") val id: String,
9 | @Json(name = "body") val htmlPage: String,
10 | @Json(name = "lang") val lang: String,
11 | @Json(name = "background") val background: String,
12 | @Json(name = "object") val obj: String
13 | )
14 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/config/CacheConfig.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.dto.config
2 |
3 | import androidx.annotation.RawRes
4 | import com.qonversion.android.sdk.dto.entitlements.QEntitlementsCacheLifetime
5 |
6 | internal data class CacheConfig(
7 | val entitlementsCacheLifetime: QEntitlementsCacheLifetime,
8 | @RawRes val fallbackFileIdentifier: Int?
9 | )
10 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/config/PrimaryConfig.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.dto.config
2 |
3 | import com.qonversion.android.sdk.BuildConfig
4 | import com.qonversion.android.sdk.dto.QEnvironment
5 | import com.qonversion.android.sdk.dto.QLaunchMode
6 |
7 | internal data class PrimaryConfig(
8 | val projectKey: String,
9 | val launchMode: QLaunchMode,
10 | val environment: QEnvironment,
11 | val proxyUrl: String? = null,
12 | val isKidsMode: Boolean = false,
13 | val sendFbAttribution: Boolean = true,
14 | val sdkVersion: String = BuildConfig.VERSION_NAME
15 | )
16 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/device/Os.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.dto.device
2 |
3 | import android.os.Build
4 | import com.squareup.moshi.Json
5 | import com.squareup.moshi.JsonClass
6 |
7 | @JsonClass(generateAdapter = true)
8 | internal data class Os(
9 | @Json(name = "name") val name: String = "Android",
10 | @Json(name = "version") val version: String = Build.VERSION.SDK_INT.toString()
11 | )
12 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/eligibility/EligibilityResult.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.dto.eligibility
2 |
3 | import com.qonversion.android.sdk.dto.eligibility.QEligibility
4 | import com.squareup.moshi.Json
5 | import com.squareup.moshi.JsonClass
6 |
7 | @JsonClass(generateAdapter = true)
8 | internal data class EligibilityResult(
9 | @Json(name = "products_enriched") val productsEligibility: Map
10 | )
11 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/eligibility/ProductEligibility.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.dto.eligibility
2 |
3 | import com.qonversion.android.sdk.dto.eligibility.QIntroEligibilityStatus
4 | import com.qonversion.android.sdk.dto.products.QProduct
5 | import com.squareup.moshi.Json
6 | import com.squareup.moshi.JsonClass
7 |
8 | @JsonClass(generateAdapter = true)
9 | internal data class ProductEligibility(
10 | @Json(name = "product") val product: QProduct,
11 | @Json(name = "intro_eligibility_status") val eligibilityStatus: QIntroEligibilityStatus
12 | )
13 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/eligibility/StoreProductInfo.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.dto.eligibility
2 |
3 | import com.squareup.moshi.Json
4 | import com.squareup.moshi.JsonClass
5 |
6 | @JsonClass(generateAdapter = true)
7 | internal data class StoreProductInfo(
8 | @Json(name = "store_id") val storeId: String
9 | )
10 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/identity/IdentityResult.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.dto.identity
2 |
3 | import com.squareup.moshi.Json
4 | import com.squareup.moshi.JsonClass
5 |
6 | @JsonClass(generateAdapter = true)
7 | internal data class IdentityResult(
8 | @Json(name = "anon_id") val userID: String
9 | )
10 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/purchase/History.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.dto.purchase
2 |
3 | import com.squareup.moshi.Json
4 | import com.squareup.moshi.JsonClass
5 |
6 | @JsonClass(generateAdapter = true)
7 | internal data class History(
8 | @Json(name = "product") val product: String,
9 | @Json(name = "purchase_token") val purchaseToken: String,
10 | @Json(name = "purchase_time") val purchaseTime: Long
11 | )
12 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/purchase/Inapp.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.dto.purchase
2 |
3 | import com.squareup.moshi.Json
4 | import com.squareup.moshi.JsonClass
5 |
6 | @JsonClass(generateAdapter = true)
7 | internal data class Inapp(
8 | @Json(name = "purchase") val purchase: PurchaseDetails,
9 | )
10 |
11 | @JsonClass(generateAdapter = true)
12 | internal data class PurchaseDetails(
13 | @Json(name = "purchase_token") val purchaseToken: String,
14 | @Json(name = "purchase_time") val purchaseTime: Long,
15 | @Json(name = "transaction_id") val transactionId: String,
16 | @Json(name = "original_transaction_id") val originalTransactionId: String,
17 | @Json(name = "product") val storeProductId: String,
18 | @Json(name = "product_id") val qProductId: String,
19 | @Json(name = "context_keys") val contextKeys: List?,
20 | @Json(name = "screen_uid") val screenUid: String?,
21 | )
22 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/purchase/PurchaseModelInternal.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.dto.purchase
2 |
3 | import com.qonversion.android.sdk.dto.QPurchaseModel
4 | import com.qonversion.android.sdk.dto.QPurchaseOptions
5 | import com.qonversion.android.sdk.dto.QPurchaseUpdateModel
6 | import com.qonversion.android.sdk.dto.QPurchaseUpdatePolicy
7 | import com.qonversion.android.sdk.dto.products.QProduct
8 |
9 | internal open class PurchaseModelInternal(
10 | val productId: String,
11 | val oldProductId: String?,
12 | val updatePolicy: QPurchaseUpdatePolicy?,
13 | val options: QPurchaseOptions?
14 | ) {
15 | constructor(purchaseModel: QPurchaseModel) : this(
16 | purchaseModel.productId,
17 | null,
18 | null,
19 | QPurchaseOptions(offerId = purchaseModel.offerId, applyOffer = purchaseModel.applyOffer)
20 | )
21 |
22 | constructor(product: QProduct, options: QPurchaseOptions? = null) : this(
23 | product.qonversionID,
24 | options?.oldProduct?.qonversionID,
25 | options?.updatePolicy,
26 | options
27 | )
28 |
29 | constructor(purchaseModel: QPurchaseUpdateModel) : this(
30 | purchaseModel.productId,
31 | purchaseModel.oldProductId,
32 | purchaseModel.updatePolicy,
33 | QPurchaseOptions(offerId = purchaseModel.offerId, applyOffer = purchaseModel.applyOffer)
34 | )
35 |
36 | fun enrich(product: QProduct, oldProduct: QProduct?) = PurchaseModelInternalEnriched(
37 | productId, product, oldProduct, updatePolicy, options
38 | )
39 | }
40 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/purchase/PurchaseModelInternalEnriched.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.dto.purchase
2 |
3 | import com.qonversion.android.sdk.dto.QPurchaseOptions
4 | import com.qonversion.android.sdk.dto.QPurchaseUpdatePolicy
5 | import com.qonversion.android.sdk.dto.products.QProduct
6 |
7 | internal class PurchaseModelInternalEnriched(
8 | productId: String,
9 | val product: QProduct,
10 | val oldProduct: QProduct?,
11 | updatePolicy: QPurchaseUpdatePolicy?,
12 | options: QPurchaseOptions?
13 | ) : PurchaseModelInternal(productId, oldProduct?.qonversionID, updatePolicy, options) {
14 |
15 | constructor(
16 | purchaseModel: PurchaseModelInternal,
17 | product: QProduct
18 | ) : this(
19 | purchaseModel.productId,
20 | product,
21 | purchaseModel.options?.oldProduct,
22 | purchaseModel.updatePolicy,
23 | purchaseModel.options
24 | )
25 | }
26 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/request/AttachUserRequest.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.dto.request
2 |
3 | import com.squareup.moshi.Json
4 | import com.squareup.moshi.JsonClass
5 |
6 | @JsonClass(generateAdapter = true)
7 | internal data class AttachUserRequest(
8 | @Json(name = "group_id") val groupId: String
9 | )
10 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/request/AttributionRequest.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.dto.request
2 |
3 | import com.qonversion.android.sdk.internal.dto.Environment
4 | import com.qonversion.android.sdk.internal.dto.ProviderData
5 | import com.squareup.moshi.Json
6 | import com.squareup.moshi.JsonClass
7 |
8 | @JsonClass(generateAdapter = true)
9 | internal data class AttributionRequest(
10 | @Json(name = "d") val d: Environment,
11 | @Json(name = "v") val v: String,
12 | @Json(name = "access_token") val accessToken: String,
13 | @Json(name = "provider_data") val providerData: ProviderData,
14 | @Json(name = "client_uid") var clientUid: String?
15 | )
16 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/request/CrashRequest.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.dto.request
2 |
3 | import com.squareup.moshi.Json
4 | import com.squareup.moshi.JsonClass
5 |
6 | @JsonClass(generateAdapter = true)
7 | internal data class CrashRequest(
8 | @Json(name = "exception") val log: ExceptionInfo,
9 | @Json(name = "device") val deviceInfo: DeviceInfo
10 | ) {
11 | @JsonClass(generateAdapter = true)
12 | data class ExceptionInfo(
13 | @Json(name = "title") val title: String,
14 | @Json(name = "place") val place: String,
15 | @Json(name = "traces") val traces: List
16 | )
17 |
18 | @JsonClass(generateAdapter = true)
19 | data class ExceptionTrace(
20 | @Json(name = "rawStackTrace") val rawStackTrace: String,
21 | @Json(name = "class") val className: String,
22 | @Json(name = "message") val message: String,
23 | @Json(name = "elements") val elements: List
24 | )
25 |
26 | @JsonClass(generateAdapter = true)
27 | data class ExceptionTraceElement(
28 | @Json(name = "class") val className: String,
29 | @Json(name = "file") val fileName: String,
30 | @Json(name = "method") val methodName: String,
31 | @Json(name = "line") val line: Int
32 | )
33 |
34 | @JsonClass(generateAdapter = true)
35 | data class DeviceInfo(
36 | @Json(name = "platform") val platform: String,
37 | @Json(name = "platform_version") val platformVersion: String,
38 | @Json(name = "source") val source: String,
39 | @Json(name = "source_version") val sourceVersion: String,
40 | @Json(name = "project_key") val projectKey: String,
41 | @Json(name = "uid") val uid: String
42 | )
43 | }
44 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/request/EligibilityRequest.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.dto.request
2 |
3 | import com.qonversion.android.sdk.internal.dto.Environment
4 | import com.qonversion.android.sdk.internal.dto.eligibility.StoreProductInfo
5 | import com.squareup.moshi.Json
6 | import com.squareup.moshi.JsonClass
7 |
8 | @JsonClass(generateAdapter = true)
9 | internal data class EligibilityRequest(
10 | @Json(name = "install_date") override val installDate: Long,
11 | @Json(name = "device") override val device: Environment,
12 | @Json(name = "version") override val version: String,
13 | @Json(name = "access_token") override val accessToken: String,
14 | @Json(name = "q_uid") override val clientUid: String?,
15 | @Json(name = "receipt") override val receipt: String = "",
16 | @Json(name = "debug_mode") override val debugMode: String,
17 | @Json(name = "products_local_data") val productInfos: List
18 | ) : RequestData()
19 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/request/IdentityRequest.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.dto.request
2 |
3 | import com.squareup.moshi.Json
4 | import com.squareup.moshi.JsonClass
5 |
6 | @JsonClass(generateAdapter = true)
7 | internal data class IdentityRequest(
8 | @Json(name = "anon_id") val anonID: String,
9 | @Json(name = "identity_id") val identityID: String
10 | )
11 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/request/InitRequest.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.dto.request
2 |
3 | import com.qonversion.android.sdk.internal.dto.Environment
4 | import com.qonversion.android.sdk.internal.dto.purchase.Inapp
5 | import com.squareup.moshi.Json
6 | import com.squareup.moshi.JsonClass
7 |
8 | @JsonClass(generateAdapter = true)
9 | internal data class InitRequest(
10 | @Json(name = "install_date") override val installDate: Long,
11 | @Json(name = "device") override val device: Environment,
12 | @Json(name = "version") override val version: String,
13 | @Json(name = "access_token") override val accessToken: String,
14 | @Json(name = "q_uid") override val clientUid: String?,
15 | @Json(name = "receipt") override val receipt: String = "",
16 | @Json(name = "debug_mode") override val debugMode: String,
17 | @Json(name = "purchases") val purchases: List? = null
18 | ) : RequestData()
19 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/request/PurchaseRequest.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.dto.request
2 |
3 | import com.qonversion.android.sdk.internal.dto.Environment
4 | import com.qonversion.android.sdk.internal.dto.purchase.PurchaseDetails
5 | import com.squareup.moshi.Json
6 | import com.squareup.moshi.JsonClass
7 |
8 | @JsonClass(generateAdapter = true)
9 | internal data class PurchaseRequest(
10 | @Json(name = "install_date") override val installDate: Long,
11 | @Json(name = "device") override val device: Environment,
12 | @Json(name = "version") override val version: String,
13 | @Json(name = "access_token") override val accessToken: String,
14 | @Json(name = "q_uid") override val clientUid: String?,
15 | @Json(name = "receipt") override val receipt: String = "",
16 | @Json(name = "debug_mode") override val debugMode: String,
17 | @Json(name = "purchase") val purchase: PurchaseDetails,
18 | ) : RequestData()
19 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/request/RequestData.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.dto.request
2 |
3 | import com.qonversion.android.sdk.internal.dto.Environment
4 |
5 | internal abstract class RequestData {
6 | abstract val installDate: Long
7 | abstract val device: Environment
8 | abstract val version: String
9 | abstract val accessToken: String
10 | abstract val clientUid: String?
11 | abstract val receipt: String
12 | abstract val debugMode: String
13 | }
14 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/request/RestoreRequest.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.dto.request
2 |
3 | import com.qonversion.android.sdk.internal.dto.Environment
4 | import com.qonversion.android.sdk.internal.dto.purchase.History
5 | import com.squareup.moshi.Json
6 | import com.squareup.moshi.JsonClass
7 |
8 | @JsonClass(generateAdapter = true)
9 | internal data class RestoreRequest(
10 | @Json(name = "install_date") override val installDate: Long,
11 | @Json(name = "device") override val device: Environment,
12 | @Json(name = "version") override val version: String,
13 | @Json(name = "access_token") override val accessToken: String,
14 | @Json(name = "q_uid") override val clientUid: String?,
15 | @Json(name = "receipt") override val receipt: String = "",
16 | @Json(name = "debug_mode") override val debugMode: String,
17 | @Json(name = "history") val history: List
18 | ) : RequestData()
19 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/request/SendPushTokenRequest.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.dto.request
2 |
3 | import com.squareup.moshi.Json
4 | import com.squareup.moshi.JsonClass
5 |
6 | @JsonClass(generateAdapter = true)
7 | data class SendPushTokenRequest(
8 | @Json(name = "access_token") val accessToken: String,
9 | @Json(name = "q_uid") val clientUid: String?,
10 | @Json(name = "device_id") val deviceId: String,
11 | @Json(name = "push_token") val pushToken: String
12 | )
13 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/request/ViewsRequest.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.dto.request
2 |
3 | import com.squareup.moshi.Json
4 | import com.squareup.moshi.JsonClass
5 |
6 | @JsonClass(generateAdapter = true)
7 | internal data class ViewsRequest(
8 | @Json(name = "user") val userID: String
9 | )
10 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/request/data/InitRequestData.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.dto.request.data
2 |
3 | import com.qonversion.android.sdk.internal.api.RequestTrigger
4 | import com.qonversion.android.sdk.listeners.QonversionLaunchCallback
5 | import com.qonversion.android.sdk.internal.purchase.Purchase
6 |
7 | internal data class InitRequestData(
8 | val installDate: Long,
9 | val idfa: String? = null,
10 | val purchases: List? = null,
11 | val callback: QonversionLaunchCallback? = null,
12 | val requestTrigger: RequestTrigger,
13 | )
14 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/dto/request/data/UserPropertyRequestData.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.dto.request.data
2 |
3 | import com.squareup.moshi.Json
4 | import com.squareup.moshi.JsonClass
5 |
6 | @JsonClass(generateAdapter = true)
7 | internal data class UserPropertyRequestData(
8 | @Json(name = "key") val key: String,
9 | @Json(name = "value") val value: String
10 | )
11 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/logger/ConsoleLogger.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.logger
2 |
3 | import android.util.Log
4 | import com.qonversion.android.sdk.BuildConfig
5 |
6 | internal class ConsoleLogger : Logger {
7 | override fun error(message: String) {
8 | log(Log.ERROR, message)
9 | }
10 |
11 | override fun warn(message: String) {
12 | log(Log.WARN, message)
13 | }
14 |
15 | override fun release(message: String) {
16 | log(Log.INFO, message)
17 | }
18 |
19 | override fun debug(message: String) {
20 | if (BuildConfig.DEBUG) {
21 | log(Log.DEBUG, message)
22 | }
23 | }
24 |
25 | private fun log(logLevel: Int, message: String) {
26 | Log.println(logLevel, TAG, format(message))
27 | }
28 |
29 | private fun format(message: String): String {
30 | return "[Thread - " + Thread.currentThread().name + "] " + message
31 | }
32 |
33 | companion object {
34 | private const val TAG = "Qonversion"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/logger/ExceptionManager.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.logger
2 |
3 | import android.content.Context
4 |
5 | interface ExceptionManager {
6 | fun initialize(context: Context)
7 | }
8 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/logger/Logger.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.logger
2 |
3 | internal interface Logger {
4 |
5 | fun error(message: String)
6 |
7 | fun warn(message: String)
8 |
9 | fun release(message: String)
10 |
11 | fun debug(message: String)
12 | }
13 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/provider/AppStateProvider.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.provider
2 |
3 | import com.qonversion.android.sdk.internal.AppState
4 |
5 | internal interface AppStateProvider {
6 |
7 | val appState: AppState
8 | }
9 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/provider/CacheConfigProvider.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.provider
2 |
3 | import com.qonversion.android.sdk.internal.dto.config.CacheConfig
4 |
5 | internal interface CacheConfigProvider {
6 |
7 | val cacheConfig: CacheConfig
8 | }
9 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/provider/EntitlementsUpdateListenerProvider.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.provider
2 |
3 | import com.qonversion.android.sdk.listeners.QEntitlementsUpdateListener
4 |
5 | internal interface EntitlementsUpdateListenerProvider {
6 |
7 | val entitlementsUpdateListener: QEntitlementsUpdateListener?
8 | }
9 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/provider/EnvironmentProvider.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.provider
2 |
3 | import com.qonversion.android.sdk.dto.QEnvironment
4 |
5 | internal interface EnvironmentProvider {
6 |
7 | val apiUrl: String
8 |
9 | val environment: QEnvironment
10 |
11 | val isSandbox: Boolean
12 | }
13 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/provider/PrimaryConfigProvider.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.provider
2 |
3 | import com.qonversion.android.sdk.internal.dto.config.PrimaryConfig
4 |
5 | internal interface PrimaryConfigProvider {
6 |
7 | val primaryConfig: PrimaryConfig
8 | }
9 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/provider/UidProvider.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.provider
2 |
3 | // todo should be removed when UserInfoProvider will be implemented
4 | internal interface UidProvider {
5 |
6 | val uid: String
7 | }
8 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/provider/UserStateProvider.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.provider
2 |
3 | internal interface UserStateProvider {
4 | val isUserStable: Boolean
5 | }
6 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/purchase/Purchase.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.purchase
2 |
3 | import com.squareup.moshi.JsonClass
4 |
5 | @JsonClass(generateAdapter = true)
6 | internal data class Purchase(
7 | val storeProductId: String?,
8 | val orderId: String,
9 | val originalOrderId: String,
10 | val purchaseTime: Long,
11 | val purchaseToken: String,
12 | val contextKeys: List?,
13 | val screenUid: String?,
14 | )
15 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/purchase/PurchaseHistory.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.purchase
2 |
3 | import com.android.billingclient.api.PurchaseHistoryRecord
4 | import com.qonversion.android.sdk.internal.dto.QStoreProductType
5 |
6 | internal data class PurchaseHistory(
7 | val type: QStoreProductType,
8 | val historyRecord: PurchaseHistoryRecord
9 | )
10 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/storage/Cache.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.storage
2 |
3 | import com.squareup.moshi.JsonAdapter
4 |
5 | internal interface Cache {
6 |
7 | fun putInt(key: String, value: Int)
8 | /**
9 | * @param defValue is returned if the Int preference for key does not exist
10 | */
11 | fun getInt(key: String, defValue: Int): Int
12 |
13 | fun getBool(key: String, defValue: Boolean = false): Boolean
14 |
15 | fun putBool(key: String, value: Boolean)
16 |
17 | fun putFloat(key: String, value: Float)
18 | /**
19 | * @param defValue is returned if the Float preference for key does not exist
20 | */
21 | fun getFloat(key: String, defValue: Float): Float
22 |
23 | fun putLong(key: String, value: Long)
24 | /**
25 | * @param defValue is returned if the Long preference for key does not exist
26 | */
27 | fun getLong(key: String, defValue: Long): Long
28 |
29 | fun putString(key: String, value: String?)
30 | /**
31 | * @param defValue is returned if the String preference for key does not exist
32 | */
33 | fun getString(key: String, defValue: String?): String?
34 |
35 | fun putObject(key: String, value: T, adapter: JsonAdapter)
36 |
37 | fun getObject(key: String, adapter: JsonAdapter): T?
38 |
39 | fun remove(key: String)
40 | }
41 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/storage/PropertiesStorage.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.storage
2 |
3 | internal interface PropertiesStorage {
4 | fun save(key: String, value: String)
5 |
6 | fun clear(properties: Map)
7 |
8 | fun getProperties(): Map
9 | }
10 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/storage/Storage.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.storage
2 |
3 | internal interface Storage {
4 |
5 | fun load(): String
6 |
7 | fun delete()
8 | }
9 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/storage/TokenStorage.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.storage
2 |
3 | import android.content.SharedPreferences
4 |
5 | internal class TokenStorage(private val preferences: SharedPreferences) : Storage {
6 |
7 | companion object {
8 | private const val TOKEN_KEY = "token_key"
9 | }
10 |
11 | override fun load(): String {
12 | return preferences.getString(TOKEN_KEY, "") ?: ""
13 | }
14 |
15 | override fun delete() {
16 | preferences.edit().remove(TOKEN_KEY).apply()
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/storage/UserPropertiesStorage.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.storage
2 |
3 | import java.util.concurrent.ConcurrentHashMap
4 |
5 | internal class UserPropertiesStorage : PropertiesStorage {
6 | private val userProperties: MutableMap =
7 | ConcurrentHashMap()
8 |
9 | override fun save(key: String, value: String) {
10 | userProperties[key] = value
11 | }
12 |
13 | override fun clear(properties: Map) {
14 | properties.keys.map {
15 | userProperties.remove(it)
16 | }
17 | }
18 |
19 | override fun getProperties(): Map {
20 | return userProperties.toMap()
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/internal/utils.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal
2 |
3 | import com.qonversion.android.sdk.dto.QonversionError
4 | import com.qonversion.android.sdk.dto.QonversionErrorCode
5 |
6 | internal val Int.daysToSeconds get() = this * 24L * 60 * 60
7 |
8 | internal val Int.daysToMs get() = daysToSeconds * 1000
9 |
10 | internal val QonversionError.shouldFireFallback
11 | get(): Boolean =
12 | this.code == QonversionErrorCode.NetworkConnectionFailed ||
13 | this.httpCode?.isInternalServerError() == true
14 |
--------------------------------------------------------------------------------
/sdk/src/main/java/com/qonversion/android/sdk/listeners/QEntitlementsUpdateListener.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.listeners
2 |
3 | import com.qonversion.android.sdk.dto.entitlements.QEntitlement
4 |
5 | /**
6 | * The listener of user entitlements updates.
7 | *
8 | * It can be provided to the [QonversionConfig](com.qonversion.android.sdk.QonversionConfig)
9 | * via [QonversionConfig.Builder.setEntitlementsUpdateListener](com.qonversion.android.sdk.QonversionConfig.Builder.setEntitlementsUpdateListener)
10 | * or set directly to the current [Qonversion](com.qonversion.android.sdk.Qonversion) instance
11 | * via [Qonversion.setEntitlementsUpdateListener](com.qonversion.android.sdk.Qonversion.setEntitlementsUpdateListener).
12 | */
13 | interface QEntitlementsUpdateListener {
14 |
15 | /**
16 | * Called when user entitlements are updated asynchronously. For example when the purchase is made
17 | * with SCA or parental control and thus needs additional confirmation.
18 | *
19 | * @param entitlements all the current entitlements of the user.
20 | */
21 | fun onEntitlementsUpdated(entitlements: Map)
22 | }
23 |
--------------------------------------------------------------------------------
/sdk/src/main/res/anim/q_fade_in.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/sdk/src/main/res/anim/q_fade_out.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/sdk/src/main/res/anim/q_slide_in_from_bottom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
7 |
8 |
--------------------------------------------------------------------------------
/sdk/src/main/res/anim/q_slide_in_from_left.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/sdk/src/main/res/anim/q_slide_out_to_bottom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
7 |
8 |
--------------------------------------------------------------------------------
/sdk/src/main/res/anim/q_slide_out_to_left.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/sdk/src/main/res/layout/q_activity_screen.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/sdk/src/main/res/layout/q_fragment_screen.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
13 |
17 |
--------------------------------------------------------------------------------
/sdk/src/main/res/layout/q_progress_bar.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
14 |
15 |
--------------------------------------------------------------------------------
/sdk/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #5C5C5C
4 |
5 |
--------------------------------------------------------------------------------
/sdk/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
--------------------------------------------------------------------------------
/sdk/src/test/java/com/qonversion/android/sdk/internal/api/ApiHelperTest.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.api
2 |
3 | import io.mockk.clearAllMocks
4 | import io.mockk.every
5 | import io.mockk.mockk
6 | import okhttp3.Request
7 | import org.assertj.core.api.Assertions
8 | import org.junit.jupiter.api.BeforeEach
9 | import org.junit.jupiter.api.Nested
10 | import org.junit.jupiter.api.Test
11 |
12 | internal class ApiHelperTest {
13 |
14 | private val apiUrl = "https://api.qonversion.io/"
15 | private lateinit var apiHelper: ApiHelper
16 |
17 | @BeforeEach
18 | fun setUp() {
19 | clearAllMocks()
20 |
21 | apiHelper = ApiHelper(apiUrl)
22 | }
23 |
24 | @Nested
25 | inner class IsV1Request {
26 | @Test
27 | fun `should return true when request version is 1`() {
28 | // given
29 | val url = "${apiUrl}v1/user/init"
30 | val request = mockRequestWithUrl(url)
31 |
32 | // when
33 | val result = apiHelper.isV1Request(request)
34 |
35 | // then
36 | Assertions.assertThat(result).isTrue()
37 | }
38 |
39 | @Test
40 | fun `should return false when request version is 2`() {
41 | // given
42 | val url = "${apiUrl}v2/screens/id"
43 | val request = mockRequestWithUrl(url)
44 |
45 | // when
46 | val result = apiHelper.isV1Request(request)
47 |
48 | // then
49 | Assertions.assertThat(result).isFalse()
50 | }
51 |
52 | private fun mockRequestWithUrl(url: String): Request {
53 | val request = mockk()
54 | every {
55 | request.url().toString()
56 | } returns url
57 |
58 | return request
59 | }
60 | }
61 | }
--------------------------------------------------------------------------------
/sdk/src/test/java/com/qonversion/android/sdk/internal/requests/AppRequestTest.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.requests
2 |
3 | import com.qonversion.android.sdk.internal.dto.app.App
4 | import com.squareup.moshi.JsonAdapter
5 | import com.squareup.moshi.Moshi
6 | import org.json.JSONObject
7 | import org.junit.Assert
8 | import org.junit.Before
9 | import org.junit.Test
10 | import org.junit.runner.RunWith
11 | import org.robolectric.RobolectricTestRunner
12 |
13 | @RunWith(RobolectricTestRunner::class)
14 | internal class AppRequestTest {
15 |
16 | private lateinit var adapter: JsonAdapter
17 |
18 | @Before
19 | fun setup() {
20 | val moshi = Moshi.Builder().build()
21 | adapter = moshi.adapter(App::class.java)
22 | }
23 |
24 | @Test
25 | fun appRequestWithCorrectData() {
26 | val json = adapter.toJson(App(
27 | name = "app_name",
28 | build = "app_build",
29 | bundle = "app_bundle",
30 | version = "app_version"
31 | ))
32 | val jsonObj = JSONObject(json)
33 |
34 | Assert.assertTrue(jsonObj.has("name"))
35 | Assert.assertTrue(jsonObj.has("build"))
36 | Assert.assertTrue(jsonObj.has("bundle"))
37 | Assert.assertTrue(jsonObj.has("version"))
38 |
39 | Assert.assertEquals("app_name", jsonObj.get("name"))
40 | Assert.assertEquals("app_build", jsonObj.get("build"))
41 | Assert.assertEquals("app_bundle", jsonObj.get("bundle"))
42 | Assert.assertEquals("app_version", jsonObj.get("version"))
43 | }
44 | }
--------------------------------------------------------------------------------
/sdk/src/test/java/com/qonversion/android/sdk/internal/requests/OsRequestTest.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.requests
2 |
3 | import android.os.Build
4 | import com.qonversion.android.sdk.internal.dto.device.Os
5 | import com.squareup.moshi.JsonAdapter
6 | import com.squareup.moshi.Moshi
7 | import org.json.JSONObject
8 | import org.junit.Assert
9 | import org.junit.Before
10 | import org.junit.Test
11 | import org.junit.runner.RunWith
12 | import org.robolectric.RobolectricTestRunner
13 |
14 | @RunWith(RobolectricTestRunner::class)
15 | internal class OsRequestTest {
16 |
17 | private lateinit var adapter: JsonAdapter
18 |
19 | @Before
20 | fun setup() {
21 | val moshi = Moshi.Builder().build()
22 | adapter = moshi.adapter(Os::class.java)
23 | }
24 |
25 | @Test
26 | fun osRequestWithCorrectData() {
27 | val json = adapter.toJson(Os())
28 | val jsonObj = JSONObject(json)
29 | Assert.assertTrue(jsonObj.has("version"))
30 | Assert.assertTrue(jsonObj.has("name"))
31 | Assert.assertEquals("Android", jsonObj.get("name"))
32 | Assert.assertEquals(Build.VERSION.SDK_INT.toString(), jsonObj.get("version"))
33 | }
34 | }
--------------------------------------------------------------------------------
/sdk/src/test/java/com/qonversion/android/sdk/internal/storage/UserPropertiesStorageTest.kt:
--------------------------------------------------------------------------------
1 | package com.qonversion.android.sdk.internal.storage
2 |
3 | import org.junit.Assert
4 | import org.junit.Before
5 | import org.junit.Test
6 | import org.junit.runner.RunWith
7 | import org.robolectric.RobolectricTestRunner
8 |
9 | @RunWith(RobolectricTestRunner::class)
10 | internal class UserPropertiesStorageTest {
11 | private lateinit var userPropertiesStorage: UserPropertiesStorage
12 |
13 | @Before
14 | fun setUp() {
15 | userPropertiesStorage = UserPropertiesStorage()
16 | }
17 |
18 | @Test
19 | fun saveProperties() {
20 | userPropertiesStorage.save("any_string_key_1", "any_string_value_1")
21 | userPropertiesStorage.save("any_string_key_2", "any_string_value_2")
22 | userPropertiesStorage.save("any_string_key_1", "new_string_value_1")
23 | val properties: Map = userPropertiesStorage.getProperties()
24 |
25 | Assert.assertTrue(properties.isNotEmpty())
26 | Assert.assertTrue(properties.containsKey("any_string_key_1"))
27 | Assert.assertTrue(properties.containsKey("any_string_key_2"))
28 | Assert.assertEquals(properties.getValue("any_string_key_1"), "new_string_value_1")
29 | Assert.assertEquals(properties.getValue("any_string_key_2"), "any_string_value_2")
30 | }
31 |
32 | @Test
33 | fun saveAndClearProperties() {
34 | userPropertiesStorage.save("any_string_key_1", "any_string_value_1")
35 | userPropertiesStorage.save("any_string_key_2", "any_string_value_2")
36 |
37 | userPropertiesStorage.clear(mapOf("any_string_key_1" to "any_string_value_1",
38 | "any_string_key_2" to "any_string_value_2"))
39 | Assert.assertTrue(userPropertiesStorage.getProperties().isEmpty())
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | // https://kotlinlang.org/docs/gradle-configure-project.html#set-jdk-version-with-the-task-dsl
3 | id("org.gradle.toolchains.foojay-resolver-convention") version("0.9.0")
4 | }
5 |
6 | include ':sdk', ':sample', ':nocodes'
--------------------------------------------------------------------------------