├── .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 | 3 | 4 | 8 | 9 | 13 | 14 | 18 | 19 | -------------------------------------------------------------------------------- /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' --------------------------------------------------------------------------------