diff --git a/.circleci/config.yml b/.circleci/config.yml
index 4dab7a8ec..889165908 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -48,30 +48,6 @@ commands:
name: Install Android SDK platform tools
command: sdkmanager "platform-tools"
- install-cocoapods-on-macos:
- steps:
- - run:
- name: Install CocoaPods
- command: bundle exec pod install --repo-update
- working_directory: iosApp
-
- pre-install-pods-for-kotlin-modules:
- description: >
- Run podInstallSyntheticIos for all Kotlin modules sequentially.
- Parallel pod installs race on the shared CocoaPods cache, causing
- Errno::ENOENT failures. Running them first with --max-workers=1
- lets the subsequent compile step skip them (UP-TO-DATE).
- steps:
- - run:
- name: Pre-install CocoaPods for Kotlin modules
- command: |
- ./gradlew \
- :models:podInstallSyntheticIos \
- :mappings:podInstallSyntheticIos \
- :core:podInstallSyntheticIos \
- :revenuecatui:podInstallSyntheticIos \
- --no-parallel --max-workers=1
-
save-kotlin-native-compiler-to-cache:
steps:
- run:
@@ -109,7 +85,7 @@ commands:
- persist_to_workspace:
name: Save incremental Gradle build to workspace
root: ./
- paths:
+ paths:
- .gradle
# This doesn't support directory glob patterns (**), unfortunately.
- build
@@ -122,17 +98,24 @@ commands:
name: Attach workspace at working directory
at: ./
+ checkout-submodule:
+ description: "Checks out a git submodule."
+ parameters:
+ path:
+ type: string
+ default: ""
+ steps:
+ - run:
+ name: Check out submodule << parameters.path >>
+ command: |
+ git submodule init << parameters.path >>
+ git submodule update << parameters.path >>
+
parameters:
action:
type: enum
- enum: [ build, upgrade-hybrid-common, bump ]
+ enum: [ build, bump ]
default: build
- automatic:
- type: boolean
- default: false
- version:
- type: string
- default: ''
executors:
android202409:
@@ -155,24 +138,6 @@ executors:
HOMEBREW_NO_AUTO_UPDATE: 1
jobs:
- update-purchases-hybrid-common-version:
- description: "Opens a PR updating the purchases-hybrid-common dependency to the latest version."
- executor: xcode16
- steps:
- - checkout
- - pin-ruby-version
- - revenuecat/install-gem-mac-dependencies:
- cache-version: v1
- - revenuecat/trust-github-key
- - revenuecat/setup-git-credentials
- - run:
- name: Update purchases-hybrid-common to << pipeline.parameters.version >>
- command: |
- bundle exec fastlane update_hybrid_common \
- version:<< pipeline.parameters.version >> \
- open_pr:true \
- automatic_release:<< pipeline.parameters.automatic >>
-
prepare-next-snapshot-version:
description: "Opens a PR updating the SDK version to the next minor, appending -SNAPSHOT."
executor: ruby3
@@ -187,9 +152,10 @@ jobs:
command: bundle exec fastlane prepare_next_snapshot_version
detekt:
- executor: jdk17
+ executor: xcode16
steps:
- checkout
+ - checkout-submodule
- restore-gradle-user-home-directory-from-cache
- run:
name: Run Detekt on commonMain
@@ -207,6 +173,7 @@ jobs:
steps:
- install-android-sdk-on-macos
- checkout
+ - checkout-submodule
- restore-gradle-user-home-directory-from-cache
- restore-kotlin-native-compiler-from-cache
- attach-workspace-at-working-directory
@@ -227,7 +194,7 @@ jobs:
name: Build libraries for Android
command: |
tasks=()
- for module in core revenuecatui datetime either result; do
+ for module in core revenuecatui either result; do
for buildType in Debug Release; do
tasks+=(":$module:compile${buildType}KotlinAndroid")
done
@@ -240,19 +207,18 @@ jobs:
executor: xcode16
steps:
- checkout
+ - checkout-submodule
- restore-gradle-user-home-directory-from-cache
- restore-kotlin-native-compiler-from-cache
- attach-workspace-at-working-directory
- pin-ruby-version
- revenuecat/install-gem-mac-dependencies:
cache-version: v1
- - install-cocoapods-on-macos
- - pre-install-pods-for-kotlin-modules
- run:
name: Build libraries for iOS
command: |
tasks=()
- for module in core revenuecatui datetime either result; do
+ for module in core revenuecatui either result; do
for arch in Arm64 SimulatorArm64 X64; do
tasks+=(":$module:compileKotlinIos$arch")
done
@@ -283,21 +249,30 @@ jobs:
executor: xcode16
steps:
- checkout
+ - checkout-submodule
- restore-gradle-user-home-directory-from-cache
- restore-kotlin-native-compiler-from-cache
- attach-workspace-at-working-directory
- pin-ruby-version
- revenuecat/install-gem-mac-dependencies:
cache-version: v1
- - install-cocoapods-on-macos
- run:
- name: Build sample app for iOS
+ name: Build Kotlin code for iOS sample app for every architecture
command: |
tasks=()
for arch in Arm64 SimulatorArm64 X64; do
tasks+=(":composeApp:compileKotlinIos$arch")
done
./gradlew "${tasks[@]}"
+ - run:
+ name: Build iOS sample app
+ command: |
+ xcodebuild \
+ -project iosApp/iosApp.xcodeproj \
+ -scheme iosApp \
+ -configuration Debug \
+ -sdk iphonesimulator \
+ build;
- save-gradle-user-home-directory-to-cache
- save-kotlin-native-compiler-to-cache
- save-incremental-gradle-build-to-workspace
@@ -323,13 +298,13 @@ jobs:
executor: xcode16
steps:
- checkout
+ - checkout-submodule
- restore-gradle-user-home-directory-from-cache
- restore-kotlin-native-compiler-from-cache
- attach-workspace-at-working-directory
- pin-ruby-version
- revenuecat/install-gem-mac-dependencies:
cache-version: v1
- - install-cocoapods-on-macos
- run:
name: Test public API for iOS
command: |
@@ -363,6 +338,7 @@ jobs:
executor: xcode16
steps:
- checkout
+ - checkout-submodule
- restore-gradle-user-home-directory-from-cache
- restore-kotlin-native-compiler-from-cache
- attach-workspace-at-working-directory
@@ -382,6 +358,7 @@ jobs:
executor: xcode16
steps:
- checkout
+ - checkout-submodule
- restore-gradle-user-home-directory-from-cache
- attach-workspace-at-working-directory
- run:
@@ -401,11 +378,11 @@ jobs:
type: boolean
steps:
- checkout
+ - checkout-submodule
- install-android-sdk-on-macos
- pin-ruby-version
- revenuecat/install-gem-mac-dependencies:
cache-version: v1
- - install-cocoapods-on-macos
- restore-gradle-user-home-directory-from-cache
- restore-kotlin-native-compiler-from-cache
- when:
@@ -436,15 +413,6 @@ jobs:
command: bundle exec fastlane github_release_current_version
workflows:
- on-action-upgrade-hybrid-common:
- when:
- equal: [ upgrade-hybrid-common, << pipeline.parameters.action >> ]
- jobs:
- - update-purchases-hybrid-common-version:
- context:
- - git-user-ops
- - github-bot-public
-
# This workflow runs for any commit to main, as well as for any other
# branch with an open PR, except for release branches. This is controlled
# by the "Only build pull requests" setting on CircleCI.
diff --git a/.gitignore b/.gitignore
index 0b8896b3a..1a448532b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,6 @@
*.iml
.gradle
-.idea
+**/.idea/*
.DS_Store
build
captures
@@ -21,4 +21,8 @@ vendor/
.bundle/
# Share IssueNavigationConfiguration
-!.idea/vcs.xml
+!/.idea/vcs.xml
+# Ensure iosApp run configuration works
+!/.idea/xcode.xml
+!/.idea/purchases-kmp.iosApp.iml
+!/.idea/modules.xml
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 000000000..98a3307f4
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/purchases-kmp.iosApp.iml b/.idea/purchases-kmp.iosApp.iml
new file mode 100644
index 000000000..1e2970164
--- /dev/null
+++ b/.idea/purchases-kmp.iosApp.iml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/.idea/xcode.xml b/.idea/xcode.xml
new file mode 100644
index 000000000..df355d6ca
--- /dev/null
+++ b/.idea/xcode.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/.run/iosApp.run.xml b/.run/iosApp.run.xml
new file mode 100644
index 000000000..25d82394a
--- /dev/null
+++ b/.run/iosApp.run.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/AGENTS.md b/AGENTS.md
index 1799db134..f12f235c0 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -43,7 +43,6 @@ purchases-kmp/
├── models/ # Shared data models and domain objects
├── mappings/ # Platform-specific mappings
├── revenuecatui/ # Jetpack Compose UI components for paywalls
-├── datetime/ # KMP datetime utilities
├── either/ # Either/Result type implementations
├── result/ # Result wrapper types
├── build-logic/ # Custom Gradle build convention plugins
diff --git a/apiTester/build.gradle.kts b/apiTester/build.gradle.kts
index 8f1cc5d36..41788fb94 100644
--- a/apiTester/build.gradle.kts
+++ b/apiTester/build.gradle.kts
@@ -49,7 +49,6 @@ kotlin {
implementation(compose.foundation)
implementation(compose.ui)
implementation(projects.core)
- implementation(projects.datetime)
implementation(projects.either)
implementation(projects.revenuecatui)
implementation(projects.result)
diff --git a/apiTester/src/commonMain/kotlin/com/revenuecat/purchases/kmp/apitester/CustomerInfoAPI.kt b/apiTester/src/commonMain/kotlin/com/revenuecat/purchases/kmp/apitester/CustomerInfoAPI.kt
index eec72fb33..9ab5ef4c2 100644
--- a/apiTester/src/commonMain/kotlin/com/revenuecat/purchases/kmp/apitester/CustomerInfoAPI.kt
+++ b/apiTester/src/commonMain/kotlin/com/revenuecat/purchases/kmp/apitester/CustomerInfoAPI.kt
@@ -2,17 +2,12 @@
package com.revenuecat.purchases.kmp.apitester
-import com.revenuecat.purchases.kmp.datetime.allExpirationInstants
-import com.revenuecat.purchases.kmp.datetime.allPurchaseInstants
-import com.revenuecat.purchases.kmp.datetime.firstSeenInstant
-import com.revenuecat.purchases.kmp.datetime.latestExpirationInstant
-import com.revenuecat.purchases.kmp.datetime.originalPurchaseInstant
-import com.revenuecat.purchases.kmp.datetime.requestInstant
import com.revenuecat.purchases.kmp.models.CustomerInfo
import com.revenuecat.purchases.kmp.models.EntitlementInfos
import com.revenuecat.purchases.kmp.models.SubscriptionInfo
import com.revenuecat.purchases.kmp.models.Transaction
import kotlin.time.ExperimentalTime
+import kotlin.time.Instant
private class CustomerInfoAPI {
@OptIn(ExperimentalTime::class)
@@ -22,27 +17,21 @@ private class CustomerInfoAPI {
val asubs = activeSubscriptions
val productIds: Set = allPurchasedProductIdentifiers
val ledm: Long? = latestExpirationDateMillis
- val lei: kotlinx.datetime.Instant? = latestExpirationInstant
- val led: kotlin.time.Instant? = latestExpirationDate
+ val led: Instant? = latestExpirationDate
val si: Map = subscriptionsByProductIdentifier
val nst: List = nonSubscriptionTransactions
val opdm: Long? = originalPurchaseDateMillis
- val opi: kotlinx.datetime.Instant? = originalPurchaseInstant
- val opd: kotlin.time.Instant? = originalPurchaseDate
+ val opd: Instant? = originalPurchaseDate
val rdm: Long = requestDateMillis
- val ri: kotlinx.datetime.Instant = requestInstant
- val rd: kotlin.time.Instant = requestDate
+ val rd: Instant = requestDate
val fsm: Long = firstSeenMillis
- val fsi: kotlinx.datetime.Instant = firstSeenInstant
- val fs: kotlin.time.Instant = firstSeen
+ val fs: Instant = firstSeen
val oaui: String = originalAppUserId
val mu: String? = managementUrlString
val allExpirationMillisByProduct: Map = allExpirationDateMillis
- val allExpirationInstantsByProduct: Map = allExpirationInstants
- val allExpirationDatesByProduct: Map = allExpirationDates
+ val allExpirationDatesByProduct: Map = allExpirationDates
val allPurchaseMillisByProduct: Map = allPurchaseDateMillis
- val allPurchaseInstantsByProduct: Map = allPurchaseInstants
- val allPurchaseDatesByProduct: Map = allPurchaseDates
+ val allPurchaseDatesByProduct: Map = allPurchaseDates
}
}
}
diff --git a/apiTester/src/commonMain/kotlin/com/revenuecat/purchases/kmp/apitester/EntitlementInfoAPI.kt b/apiTester/src/commonMain/kotlin/com/revenuecat/purchases/kmp/apitester/EntitlementInfoAPI.kt
index 9048853bf..91ad220fb 100644
--- a/apiTester/src/commonMain/kotlin/com/revenuecat/purchases/kmp/apitester/EntitlementInfoAPI.kt
+++ b/apiTester/src/commonMain/kotlin/com/revenuecat/purchases/kmp/apitester/EntitlementInfoAPI.kt
@@ -1,12 +1,9 @@
package com.revenuecat.purchases.kmp.apitester
-import com.revenuecat.purchases.kmp.datetime.billingIssueDetectedAtInstant
-import com.revenuecat.purchases.kmp.datetime.expirationInstant
-import com.revenuecat.purchases.kmp.datetime.latestPurchaseInstant
-import com.revenuecat.purchases.kmp.datetime.originalPurchaseInstant
-import com.revenuecat.purchases.kmp.datetime.unsubscribeDetectedAtInstant
+
import com.revenuecat.purchases.kmp.models.EntitlementInfo
import kotlin.time.ExperimentalTime
+import kotlin.time.Instant
@Suppress("unused", "UNUSED_VARIABLE")
private class EntitlementInfoAPI {
@@ -19,25 +16,20 @@ private class EntitlementInfoAPI {
// FIXME re-enable in SDK-3530
// val periodType: PeriodType = periodType
val latestPurchaseDateMillis: Long? = latestPurchaseDateMillis
- val latestPurchaseInstant: kotlinx.datetime.Instant? = latestPurchaseInstant
- val latestPurchaseDate: kotlin.time.Instant? = latestPurchaseDate
+ val latestPurchaseDate: Instant? = latestPurchaseDate
val originalPurchaseDateMillis: Long? = originalPurchaseDateMillis
- val originalPurchaseInstant: kotlinx.datetime.Instant? = originalPurchaseInstant
- val originalPurchaseDate: kotlin.time.Instant? = originalPurchaseDate
+ val originalPurchaseDate: Instant? = originalPurchaseDate
val expirationDateMillis: Long? = expirationDateMillis
- val expirationInstant: kotlinx.datetime.Instant? = expirationInstant
- val expirationDate: kotlin.time.Instant? = expirationDate
+ val expirationDate: Instant? = expirationDate
// FIXME re-enable in SDK-3530
// val store: Store = store
val productIdentifier: String = productIdentifier
val productPlanIdentifier: String? = productPlanIdentifier
val sandbox: Boolean = isSandbox
val unsubscribeDetectedAtMillis: Long? = unsubscribeDetectedAtMillis
- val unsubscribeDetectedAtInstant: kotlinx.datetime.Instant? = unsubscribeDetectedAtInstant
- val unsubscribeDetectedAt: kotlin.time.Instant? = unsubscribeDetectedAt
+ val unsubscribeDetectedAt: Instant? = unsubscribeDetectedAt
val billingIssueDetectedAtMillis: Long? = billingIssueDetectedAtMillis
- val billingIssueDetectedAtInstant: kotlinx.datetime.Instant? = billingIssueDetectedAtInstant
- val billingIssueDetectedAt: kotlin.time.Instant? = billingIssueDetectedAt
+ val billingIssueDetectedAt: Instant? = billingIssueDetectedAt
// FIXME re-enable in SDK-3530
// val ownershipType: OwnershipType = ownershipType
// FIXME re-enable in SDK-3530
diff --git a/apiTester/src/commonMain/kotlin/com/revenuecat/purchases/kmp/apitester/TransactionAPI.kt b/apiTester/src/commonMain/kotlin/com/revenuecat/purchases/kmp/apitester/TransactionAPI.kt
index 4fdc61855..59f8129f7 100644
--- a/apiTester/src/commonMain/kotlin/com/revenuecat/purchases/kmp/apitester/TransactionAPI.kt
+++ b/apiTester/src/commonMain/kotlin/com/revenuecat/purchases/kmp/apitester/TransactionAPI.kt
@@ -1,8 +1,8 @@
package com.revenuecat.purchases.kmp.apitester
-import com.revenuecat.purchases.kmp.datetime.purchaseInstant
import com.revenuecat.purchases.kmp.models.Transaction
import kotlin.time.ExperimentalTime
+import kotlin.time.Instant
@Suppress("unused", "UNUSED_VARIABLE")
private class TransactionAPI {
@@ -12,8 +12,7 @@ private class TransactionAPI {
val transactionIdentifier: String = transactionIdentifier
val productIdentifier: String = productIdentifier
val purchaseDateMillis: Long = purchaseDateMillis
- val purchaseInstant: kotlinx.datetime.Instant = purchaseInstant
- val purchaseDate: kotlin.time.Instant = purchaseDate
+ val purchaseDate: Instant = purchaseDate
}
}
}
diff --git a/build-logic/convention/build.gradle.kts b/build-logic/convention/build.gradle.kts
index ea726e7b0..cb982fc92 100644
--- a/build-logic/convention/build.gradle.kts
+++ b/build-logic/convention/build.gradle.kts
@@ -32,6 +32,7 @@ dependencies {
testImplementation(libs.kotlin.test.junit)
testRuntimeOnly(libs.junit.engine)
+ testRuntimeOnly(libs.junit.platformLauncher)
}
tasks.test {
diff --git a/build-logic/convention/src/main/kotlin/com/revenuecat/purchases/kmp/buildlogic/swift/ConfigureSwiftDependencies.kt b/build-logic/convention/src/main/kotlin/com/revenuecat/purchases/kmp/buildlogic/swift/ConfigureSwiftDependencies.kt
index 2b46bade1..d5b031cba 100644
--- a/build-logic/convention/src/main/kotlin/com/revenuecat/purchases/kmp/buildlogic/swift/ConfigureSwiftDependencies.kt
+++ b/build-logic/convention/src/main/kotlin/com/revenuecat/purchases/kmp/buildlogic/swift/ConfigureSwiftDependencies.kt
@@ -10,6 +10,7 @@ import org.gradle.api.Project
import org.gradle.api.plugins.ExtensionAware
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.TaskProvider
+import org.gradle.internal.os.OperatingSystem
import org.gradle.kotlin.dsl.withType
import org.jetbrains.compose.ComposeExtension
import org.jetbrains.compose.resources.ResourcesExtension
@@ -26,6 +27,10 @@ fun Project.configureSwiftDependencies() {
afterEvaluate {
if (registry.packages.isNotEmpty()) {
+ if (!OperatingSystem.current().isMacOsX) {
+ logger.info("Skipping Swift dependency configuration on non-macOS host")
+ return@afterEvaluate
+ }
configureAllSwiftDependencies(registry.packages)
}
}
@@ -46,7 +51,7 @@ private fun Project.configureAllSwiftDependencies(dependencies: List
+ swiftDependencies: List,
+ packageInfos: Map,
) {
// Register a task to generate the cinterop .def file
val defFile = layout.buildDirectory.file("generated/cinterop/${dependency.target}.def")
@@ -130,6 +136,15 @@ private fun Project.configureSwiftDependency(
?.takeIf { it.project != this }
}
+ // Add cross-project dependency source directories so the build cache is invalidated when a
+ // dependency target's source changes.
+ val transitiveDepSourceDirs = moduleDependencies.mapNotNull { dep ->
+ packageInfos[dep.dependency.packageDir]?.getTargetSourceDir(
+ targetName = dep.dependency.target,
+ packageDir = dep.dependency.packageDir
+ )
+ }
+
kotlin.targets.withType {
// Skip this target if it doesn't include the source set this dependency is added to.
if (!includesSourceSet(dependency.sourceSetName)) return@withType
@@ -140,7 +155,13 @@ private fun Project.configureSwiftDependency(
)
val mainCompilation = compilations.getByName("main")
- val swiftBuildTask = registerSwiftBuildTask(this, dependency, targetSourceDir)
+ val swiftBuildTask = registerSwiftBuildTask(
+ kotlinTarget = this,
+ dependency = dependency,
+ targetSourceDir = targetSourceDir,
+ transitiveDepSourceDirs = transitiveDepSourceDirs
+ )
+
val swiftOutputDir = layout.buildDirectory
.dir("swift-packages/${dependency.target}/${konanTarget.name}")
.get().asFile
@@ -234,7 +255,8 @@ private fun KotlinNativeTarget.includesSourceSet(sourceSetName: String): Boolean
private fun Project.registerSwiftBuildTask(
kotlinTarget: KotlinNativeTarget,
dependency: SwiftDependency,
- targetSourceDir: File
+ targetSourceDir: File,
+ transitiveDepSourceDirs: List,
): TaskProvider {
val taskSuffix = getTaskSuffix(kotlinTarget.konanTarget)
val taskName = "compileSwift${dependency.target}$taskSuffix"
@@ -248,11 +270,13 @@ private fun Project.registerSwiftBuildTask(
moduleName.set(dependency.moduleName)
configuration.set(getSwiftConfiguration())
packageDir.set(dependency.packageDir)
+ packageSwiftFile.set(dependency.packageDir.resolve("Package.swift"))
this.targetSourceDir.set(targetSourceDir)
outputDir.set(layout.buildDirectory.dir("swift-packages/${dependency.target}/${kotlinTarget.konanTarget.name}"))
// Use a shared scratch directory at root project level for SPM build cache sharing
scratchDir.set(rootProject.layout.buildDirectory.dir("swift-packages/.build"))
swiftSettingsArgs.set(dependency.swiftSettings?.toCommandLineArgs() ?: emptyList())
+ this.transitiveDepSourceDirs.from(transitiveDepSourceDirs)
}
}
@@ -313,7 +337,7 @@ private fun Project.getSwiftConfiguration(): String {
return "debug"
}
-private fun Project.getToolchainPath(): Provider =
+private fun Project.getToolchainPath(): Provider =
providers.exec {
commandLine("xcrun", "--find", "swift")
}.standardOutput.asText.map { swiftPath ->
diff --git a/build-logic/convention/src/main/kotlin/com/revenuecat/purchases/kmp/buildlogic/swift/task/GenerateDefFileTask.kt b/build-logic/convention/src/main/kotlin/com/revenuecat/purchases/kmp/buildlogic/swift/task/GenerateDefFileTask.kt
index 2bdac4468..ecc614460 100644
--- a/build-logic/convention/src/main/kotlin/com/revenuecat/purchases/kmp/buildlogic/swift/task/GenerateDefFileTask.kt
+++ b/build-logic/convention/src/main/kotlin/com/revenuecat/purchases/kmp/buildlogic/swift/task/GenerateDefFileTask.kt
@@ -4,6 +4,8 @@ import org.gradle.api.DefaultTask
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
+import org.gradle.api.tasks.CacheableTask
+import org.gradle.api.tasks.IgnoreEmptyDirectories
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.Optional
@@ -19,6 +21,7 @@ import java.security.MessageDigest
* The .def file tells Kotlin/Native cinterop how to process the Swift-generated
* Objective-C header and link against the static library.
*/
+@CacheableTask
abstract class GenerateDefFileTask : DefaultTask() {
/** The Kotlin package name for the generated bindings */
@@ -45,6 +48,7 @@ abstract class GenerateDefFileTask : DefaultTask() {
/** The Swift source directory - used to compute a hash for cache invalidation */
@get:InputDirectory
@get:PathSensitive(PathSensitivity.RELATIVE)
+ @get:IgnoreEmptyDirectories
abstract val swiftSourceDir: DirectoryProperty
/** The output .def file */
diff --git a/build-logic/convention/src/main/kotlin/com/revenuecat/purchases/kmp/buildlogic/swift/task/ProcessSwiftResourcesTask.kt b/build-logic/convention/src/main/kotlin/com/revenuecat/purchases/kmp/buildlogic/swift/task/ProcessSwiftResourcesTask.kt
index 1eed50ab8..7f25651ab 100644
--- a/build-logic/convention/src/main/kotlin/com/revenuecat/purchases/kmp/buildlogic/swift/task/ProcessSwiftResourcesTask.kt
+++ b/build-logic/convention/src/main/kotlin/com/revenuecat/purchases/kmp/buildlogic/swift/task/ProcessSwiftResourcesTask.kt
@@ -7,6 +7,8 @@ import org.gradle.api.file.DirectoryProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.MapProperty
import org.gradle.api.provider.Property
+import org.gradle.api.tasks.CacheableTask
+import org.gradle.api.tasks.IgnoreEmptyDirectories
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.OutputDirectory
@@ -20,6 +22,7 @@ import javax.inject.Inject
/**
* Processes Swift package resources to include in the .klib via Compose Resources.
*/
+@CacheableTask
abstract class ProcessSwiftResourcesTask @Inject constructor(
private val execOperations: ExecOperations
) : DefaultTask() {
@@ -41,6 +44,7 @@ abstract class ProcessSwiftResourcesTask @Inject constructor(
*/
@get:InputFiles
@get:PathSensitive(PathSensitivity.RELATIVE)
+ @get:IgnoreEmptyDirectories
abstract val resourceFiles: ConfigurableFileCollection
/**
diff --git a/build-logic/convention/src/main/kotlin/com/revenuecat/purchases/kmp/buildlogic/swift/task/SwiftBuildTask.kt b/build-logic/convention/src/main/kotlin/com/revenuecat/purchases/kmp/buildlogic/swift/task/SwiftBuildTask.kt
index 20c48917f..edc4be63d 100644
--- a/build-logic/convention/src/main/kotlin/com/revenuecat/purchases/kmp/buildlogic/swift/task/SwiftBuildTask.kt
+++ b/build-logic/convention/src/main/kotlin/com/revenuecat/purchases/kmp/buildlogic/swift/task/SwiftBuildTask.kt
@@ -1,11 +1,17 @@
package com.revenuecat.purchases.kmp.buildlogic.swift.task
import org.gradle.api.DefaultTask
+import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.DirectoryProperty
+import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
+import org.gradle.api.tasks.CacheableTask
+import org.gradle.api.tasks.IgnoreEmptyDirectories
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputDirectory
+import org.gradle.api.tasks.InputFile
+import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputDirectory
@@ -22,6 +28,7 @@ import javax.inject.Inject
* - An Objective-C header (-Swift.h)
* - A module.modulemap for cinterop
*/
+@CacheableTask
abstract class SwiftBuildTask @Inject constructor(
private val execOperations: ExecOperations
) : DefaultTask() {
@@ -58,11 +65,27 @@ abstract class SwiftBuildTask @Inject constructor(
@get:Internal
abstract val packageDir: DirectoryProperty
+ /** The Package.swift file. Tracked for cache invalidation on package structure changes. */
+ @get:InputFile
+ @get:PathSensitive(PathSensitivity.NAME_ONLY)
+ abstract val packageSwiftFile: RegularFileProperty
+
/** The Swift target's source directory (for tracking incremental builds) */
@get:InputDirectory
@get:PathSensitive(PathSensitivity.RELATIVE)
+ @get:IgnoreEmptyDirectories
abstract val targetSourceDir: DirectoryProperty
+ /**
+ * Source directories of cross-project Swift target dependencies. Ensures the build cache
+ * is invalidated when a dependency target's source changes (e.g. RevenueCat sources
+ * affecting RevenueCatUI compilation).
+ */
+ @get:InputFiles
+ @get:PathSensitive(PathSensitivity.RELATIVE)
+ @get:IgnoreEmptyDirectories
+ abstract val transitiveDepSourceDirs: ConfigurableFileCollection
+
/** The output directory for built artifacts */
@get:OutputDirectory
abstract val outputDir: DirectoryProperty
diff --git a/build.gradle.kts b/build.gradle.kts
index ea3265e63..bc5909545 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -5,7 +5,6 @@ import com.vanniktech.maven.publish.MavenPublishPlugin
import com.vanniktech.maven.publish.SonatypeHost
import dev.iurysouza.modulegraph.Orientation
import io.gitlab.arturbosch.detekt.Detekt
-import org.gradle.configurationcache.extensions.capitalized
plugins {
//trick: for the same plugin versions in all sub-modules
@@ -14,7 +13,6 @@ plugins {
alias(libs.plugins.jetbrains.compose).apply(false)
alias(libs.plugins.compose.compiler).apply(false)
alias(libs.plugins.kotlin.multiplatform).apply(false)
- alias(libs.plugins.kotlin.cocoapods).apply(false)
alias(libs.plugins.kotlinx.binaryCompatibilityValidator)
alias(libs.plugins.adamko.dokkatoo.html)
alias(libs.plugins.arturbosch.detekt).apply(false)
@@ -110,7 +108,6 @@ apiValidation {
dependencies {
dokkatoo(projects.core)
- dokkatoo(projects.datetime)
dokkatoo(projects.either)
dokkatoo(projects.models)
dokkatoo(projects.result)
@@ -161,3 +158,6 @@ private fun TaskContainer.registerDetektTask(
xml.outputLocation = file("build/reports/detekt/$reportName.xml")
}
}
+
+private fun CharSequence.capitalized(): String =
+ toString().replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() }
diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts
index 045f4823c..3f96d5d90 100644
--- a/composeApp/build.gradle.kts
+++ b/composeApp/build.gradle.kts
@@ -48,7 +48,6 @@ kotlin {
implementation(projects.core)
implementation(projects.result)
implementation(projects.either)
- implementation(projects.datetime)
implementation(projects.revenuecatui)
}
androidMain.dependencies {
diff --git a/composeApp/src/commonMain/kotlin/com/revenuecat/purchases/kmp/sample/AdTrackingTestingScreen.kt b/composeApp/src/commonMain/kotlin/com/revenuecat/purchases/kmp/sample/AdTrackingTestingScreen.kt
index bf844582e..a06d0d452 100644
--- a/composeApp/src/commonMain/kotlin/com/revenuecat/purchases/kmp/sample/AdTrackingTestingScreen.kt
+++ b/composeApp/src/commonMain/kotlin/com/revenuecat/purchases/kmp/sample/AdTrackingTestingScreen.kt
@@ -35,7 +35,8 @@ import com.revenuecat.purchases.kmp.models.AdMediatorName
import com.revenuecat.purchases.kmp.models.AdOpenedData
import com.revenuecat.purchases.kmp.models.AdRevenueData
import com.revenuecat.purchases.kmp.models.AdRevenuePrecision
-import kotlinx.datetime.Clock
+import kotlin.time.Clock
+import kotlin.time.ExperimentalTime
enum class AdTrackingFunction(val displayName: String) {
TRACK_AD_DISPLAYED("trackAdDisplayed()"),
@@ -45,7 +46,7 @@ enum class AdTrackingFunction(val displayName: String) {
TRACK_AD_FAILED_TO_LOAD("trackAdFailedToLoad()")
}
-@OptIn(ExperimentalRevenueCatApi::class)
+@OptIn(ExperimentalRevenueCatApi::class, ExperimentalTime::class)
@Composable
fun AdTrackingTestingScreen(
navigateTo: (Screen) -> Unit
diff --git a/core/api/core.klib.api b/core/api/core.klib.api
index cad5221f0..86f945f99 100644
--- a/core/api/core.klib.api
+++ b/core/api/core.klib.api
@@ -52,8 +52,6 @@ final class com.revenuecat.purchases.kmp.ktx/SuccessfulPurchase { // com.revenue
}
final class com.revenuecat.purchases.kmp/AdTracker { // com.revenuecat.purchases.kmp/AdTracker|null[0]
- constructor () // com.revenuecat.purchases.kmp/AdTracker.|(){}[0]
-
final fun trackAdDisplayed(com.revenuecat.purchases.kmp.models/AdDisplayedData) // com.revenuecat.purchases.kmp/AdTracker.trackAdDisplayed|trackAdDisplayed(com.revenuecat.purchases.kmp.models.AdDisplayedData){}[0]
final fun trackAdFailedToLoad(com.revenuecat.purchases.kmp.models/AdFailedToLoadData) // com.revenuecat.purchases.kmp/AdTracker.trackAdFailedToLoad|trackAdFailedToLoad(com.revenuecat.purchases.kmp.models.AdFailedToLoadData){}[0]
final fun trackAdLoaded(com.revenuecat.purchases.kmp.models/AdLoadedData) // com.revenuecat.purchases.kmp/AdTracker.trackAdLoaded|trackAdLoaded(com.revenuecat.purchases.kmp.models.AdLoadedData){}[0]
diff --git a/core/build.gradle.kts b/core/build.gradle.kts
index 92dd66354..38a5ffa2f 100644
--- a/core/build.gradle.kts
+++ b/core/build.gradle.kts
@@ -2,7 +2,6 @@ import com.codingfeline.buildkonfig.compiler.FieldSpec.Type.STRING
plugins {
id("revenuecat-library")
- alias(libs.plugins.kotlin.cocoapods)
alias(libs.plugins.codingfeline.buildkonfig)
}
@@ -17,10 +16,11 @@ kotlin {
}
androidMain.dependencies {
api(libs.androidx.startup)
- implementation(libs.revenuecat.common)
+ implementation(libs.revenuecat.android)
implementation(projects.mappings)
}
iosMain.dependencies {
+ implementation(projects.knCore)
implementation(projects.mappings)
}
@@ -32,22 +32,6 @@ kotlin {
implementation(libs.kotlin.test.junit)
}
}
-
- cocoapods {
- version = libs.versions.revenuecat.kmp.get()
- ios.deploymentTarget = libs.versions.ios.deploymentTarget.core.get()
-
- framework {
- baseName = "Purchases"
- isStatic = true
- }
-
- pod("PurchasesHybridCommon") {
- version = libs.versions.revenuecat.common.get()
- extraOpts += listOf("-compiler-option", "-fmodules")
- packageName = "swiftPMImport.com.revenuecat.purchases.kn.core"
- }
- }
}
android {
@@ -59,7 +43,6 @@ buildkonfig {
defaultConfigs {
buildConfigField(STRING, "platformFlavor", "kmp")
- buildConfigField(STRING, "revenuecatCommonVersion", libs.versions.revenuecat.common.get())
buildConfigField(STRING, "revenuecatKmpVersion", libs.versions.revenuecat.kmp.get())
}
}
diff --git a/core/core.podspec b/core/core.podspec
deleted file mode 100644
index 3ac22f91d..000000000
--- a/core/core.podspec
+++ /dev/null
@@ -1,54 +0,0 @@
-Pod::Spec.new do |spec|
- spec.name = 'core'
- spec.version = '2.11.0-SNAPSHOT'
- spec.homepage = ''
- spec.source = { :http=> ''}
- spec.authors = ''
- spec.license = ''
- spec.summary = ''
- spec.vendored_frameworks = 'build/cocoapods/framework/Purchases.framework'
- spec.libraries = 'c++'
- spec.ios.deployment_target = '13.0'
- spec.dependency 'PurchasesHybridCommon', '17.55.1'
-
- if !Dir.exist?('build/cocoapods/framework/Purchases.framework') || Dir.empty?('build/cocoapods/framework/Purchases.framework')
- raise "
-
- Kotlin framework 'Purchases' doesn't exist yet, so a proper Xcode project can't be generated.
- 'pod install' should be executed after running ':generateDummyFramework' Gradle task:
-
- ./gradlew :core:generateDummyFramework
-
- Alternatively, proper pod installation is performed during Gradle sync in the IDE (if Podfile location is set)"
- end
-
- spec.xcconfig = {
- 'ENABLE_USER_SCRIPT_SANDBOXING' => 'NO',
- }
-
- spec.pod_target_xcconfig = {
- 'KOTLIN_PROJECT_PATH' => ':core',
- 'PRODUCT_MODULE_NAME' => 'Purchases',
- }
-
- spec.script_phases = [
- {
- :name => 'Build core',
- :execution_position => :before_compile,
- :shell_path => '/bin/sh',
- :script => <<-SCRIPT
- if [ "YES" = "$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED" ]; then
- echo "Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \"YES\""
- exit 0
- fi
- set -ev
- REPO_ROOT="$PODS_TARGET_SRCROOT"
- "$REPO_ROOT/../gradlew" -p "$REPO_ROOT" $KOTLIN_PROJECT_PATH:syncFramework \
- -Pkotlin.native.cocoapods.platform=$PLATFORM_NAME \
- -Pkotlin.native.cocoapods.archs="$ARCHS" \
- -Pkotlin.native.cocoapods.configuration="$CONFIGURATION"
- SCRIPT
- }
- ]
-
-end
\ No newline at end of file
diff --git a/core/src/androidMain/kotlin/com/revenuecat/purchases/kmp/Purchases.android.kt b/core/src/androidMain/kotlin/com/revenuecat/purchases/kmp/Purchases.android.kt
index c3d6ae1f9..0ff1b471b 100644
--- a/core/src/androidMain/kotlin/com/revenuecat/purchases/kmp/Purchases.android.kt
+++ b/core/src/androidMain/kotlin/com/revenuecat/purchases/kmp/Purchases.android.kt
@@ -3,8 +3,6 @@ package com.revenuecat.purchases.kmp
import android.content.Intent
import android.net.Uri
import com.revenuecat.purchases.ExperimentalPreviewRevenueCatPurchasesAPI
-import com.revenuecat.purchases.kmp.models.CustomPaywallImpressionParams as KmpCustomPaywallImpressionParams
-import com.revenuecat.purchases.paywalls.events.CustomPaywallImpressionParams as AndroidCustomPaywallImpressionParams
import com.revenuecat.purchases.PurchaseParams
import com.revenuecat.purchases.common.PlatformInfo
import com.revenuecat.purchases.getCustomerInfoWith
@@ -12,20 +10,19 @@ import com.revenuecat.purchases.getOfferingsWith
import com.revenuecat.purchases.getProductsWith
import com.revenuecat.purchases.getStorefrontCountryCodeWith
import com.revenuecat.purchases.getVirtualCurrenciesWith
-import com.revenuecat.purchases.hybridcommon.isWebPurchaseRedemptionURL
import com.revenuecat.purchases.kmp.di.AndroidProvider
import com.revenuecat.purchases.kmp.di.requireActivity
import com.revenuecat.purchases.kmp.di.requireApplication
-import com.revenuecat.purchases.kmp.mappings.toAndroid
import com.revenuecat.purchases.kmp.mappings.toAndroidBillingFeature
import com.revenuecat.purchases.kmp.mappings.toAndroidCacheFetchPolicy
+import com.revenuecat.purchases.kmp.mappings.toAndroidEntitlementVerificationMode
import com.revenuecat.purchases.kmp.mappings.toAndroidGoogleReplacementMode
import com.revenuecat.purchases.kmp.mappings.toAndroidPackage
+import com.revenuecat.purchases.kmp.mappings.toAndroidPurchasesAreCompletedBy
import com.revenuecat.purchases.kmp.mappings.toAndroidStore
import com.revenuecat.purchases.kmp.mappings.toAndroidStoreProduct
import com.revenuecat.purchases.kmp.mappings.toAndroidSubscriptionOption
import com.revenuecat.purchases.kmp.mappings.toCustomerInfo
-import com.revenuecat.purchases.kmp.mappings.toHybridString
import com.revenuecat.purchases.kmp.mappings.toOfferings
import com.revenuecat.purchases.kmp.mappings.toPurchasesError
import com.revenuecat.purchases.kmp.mappings.toStore
@@ -33,11 +30,6 @@ import com.revenuecat.purchases.kmp.mappings.toStoreProduct
import com.revenuecat.purchases.kmp.mappings.toStoreTransaction
import com.revenuecat.purchases.kmp.mappings.toVirtualCurrencies
import com.revenuecat.purchases.kmp.mappings.toWebPurchaseResult
-import com.revenuecat.purchases.kmp.models.AdDisplayedData
-import com.revenuecat.purchases.kmp.models.AdFailedToLoadData
-import com.revenuecat.purchases.kmp.models.AdLoadedData
-import com.revenuecat.purchases.kmp.models.AdOpenedData
-import com.revenuecat.purchases.kmp.models.AdRevenueData
import com.revenuecat.purchases.kmp.models.BillingFeature
import com.revenuecat.purchases.kmp.models.CacheFetchPolicy
import com.revenuecat.purchases.kmp.models.CustomerInfo
@@ -59,7 +51,6 @@ import com.revenuecat.purchases.kmp.models.StoreTransaction
import com.revenuecat.purchases.kmp.models.Storefront
import com.revenuecat.purchases.kmp.models.SubscriptionOption
import com.revenuecat.purchases.kmp.models.VirtualCurrencies
-import com.revenuecat.purchases.kmp.models.VirtualCurrency
import com.revenuecat.purchases.kmp.models.WebPurchaseRedemption
import com.revenuecat.purchases.kmp.models.WinBackOffer
import com.revenuecat.purchases.kmp.strings.ConfigureStrings
@@ -73,7 +64,9 @@ import com.revenuecat.purchases.syncPurchasesWith
import java.net.URL
import com.revenuecat.purchases.DangerousSettings as AndroidDangerousSettings
import com.revenuecat.purchases.Purchases as AndroidPurchases
-import com.revenuecat.purchases.hybridcommon.configure as commonConfigure
+import com.revenuecat.purchases.PurchasesConfiguration as AndroidPurchasesConfiguration
+import com.revenuecat.purchases.kmp.models.CustomPaywallImpressionParams as KmpCustomPaywallImpressionParams
+import com.revenuecat.purchases.paywalls.events.CustomPaywallImpressionParams as AndroidCustomPaywallImpressionParams
import com.revenuecat.purchases.virtualcurrencies.VirtualCurrencies as AndroidVirtualCurrencies
public actual class Purchases private constructor(private val androidPurchases: AndroidPurchases) {
@@ -120,21 +113,24 @@ public actual class Purchases private constructor(private val androidPurchases:
@JvmStatic
public actual fun configure(configuration: PurchasesConfiguration): Purchases {
with(configuration) {
- // Using the common configure() call allows us to pass PlatformInfo.
- commonConfigure(
- context = AndroidProvider.requireApplication(),
- apiKey = apiKey,
- appUserID = appUserId,
- purchasesAreCompletedBy = purchasesAreCompletedBy.toHybridString(),
- platformInfo = PlatformInfo(
- flavor = BuildKonfig.platformFlavor,
- version = frameworkVersion,
- ),
- store = (store ?: Store.PLAY_STORE).toAndroidStore(),
- dangerousSettings = dangerousSettings.toAndroidDangerousSettings(),
- shouldShowInAppMessagesAutomatically = showInAppMessagesAutomatically,
- verificationMode = verificationMode.name,
- pendingTransactionsForPrepaidPlansEnabled = pendingTransactionsForPrepaidPlansEnabled
+ AndroidPurchases.platformInfo = PlatformInfo(
+ flavor = BuildKonfig.platformFlavor,
+ version = frameworkVersion,
+ )
+ AndroidPurchases.configure(
+ AndroidPurchasesConfiguration.Builder(
+ context = AndroidProvider.requireApplication(),
+ apiKey = apiKey,
+ ).appUserID(appUserId)
+ .purchasesAreCompletedBy(purchasesAreCompletedBy.toAndroidPurchasesAreCompletedBy())
+ .store((store ?: Store.PLAY_STORE).toAndroidStore())
+ .dangerousSettings(dangerousSettings.toAndroidDangerousSettings())
+ .showInAppMessagesAutomatically(showInAppMessagesAutomatically)
+ .entitlementVerificationMode(verificationMode.toAndroidEntitlementVerificationMode())
+ .pendingTransactionsForPrepaidPlansEnabled(
+ pendingTransactionsForPrepaidPlansEnabled ?: false
+ )
+ .build()
)
}
@@ -154,7 +150,7 @@ public actual class Purchases private constructor(private val androidPurchases:
AndroidDangerousSettings(autoSyncPurchases)
public actual fun parseAsWebPurchaseRedemption(url: String): WebPurchaseRedemption? {
- return if (isWebPurchaseRedemptionURL(url)) {
+ return if (AndroidPurchases.parseAsWebPurchaseRedemption(url) != null) {
WebPurchaseRedemption(url)
} else {
null
diff --git a/core/src/iosMain/kotlin/com/revenuecat/purchases/kmp/AdTracker.ios.kt b/core/src/iosMain/kotlin/com/revenuecat/purchases/kmp/AdTracker.ios.kt
index e6bfac5cc..a0047460c 100644
--- a/core/src/iosMain/kotlin/com/revenuecat/purchases/kmp/AdTracker.ios.kt
+++ b/core/src/iosMain/kotlin/com/revenuecat/purchases/kmp/AdTracker.ios.kt
@@ -1,123 +1,62 @@
package com.revenuecat.purchases.kmp
-import swiftPMImport.com.revenuecat.purchases.kn.core.IOSAPIAvailabilityChecker
-import swiftPMImport.com.revenuecat.purchases.kn.core.RCCommonFunctionality
-import swiftPMImport.com.revenuecat.purchases.kn.core.trackAdDisplayed
-import swiftPMImport.com.revenuecat.purchases.kn.core.trackAdFailedToLoad
-import swiftPMImport.com.revenuecat.purchases.kn.core.trackAdLoaded
-import swiftPMImport.com.revenuecat.purchases.kn.core.trackAdOpened
-import swiftPMImport.com.revenuecat.purchases.kn.core.trackAdRevenue
+import com.revenuecat.purchases.kmp.mappings.toIos
import com.revenuecat.purchases.kmp.models.AdDisplayedData
import com.revenuecat.purchases.kmp.models.AdFailedToLoadData
import com.revenuecat.purchases.kmp.models.AdLoadedData
import com.revenuecat.purchases.kmp.models.AdOpenedData
import com.revenuecat.purchases.kmp.models.AdRevenueData
import kotlinx.cinterop.ExperimentalForeignApi
-import platform.Foundation.NSNumber
-
-private const val KEY_NETWORK_NAME = "networkName"
-private const val KEY_MEDIATOR_NAME = "mediatorName"
-private const val KEY_AD_FORMAT = "adFormat"
-private const val KEY_PLACEMENT = "placement"
-private const val KEY_AD_UNIT_ID = "adUnitId"
-private const val KEY_IMPRESSION_ID = "impressionId"
-private const val KEY_REVENUE_MICROS = "revenueMicros"
-private const val KEY_CURRENCY = "currency"
-private const val KEY_PRECISION = "precision"
-private const val KEY_MEDIATOR_ERROR_CODE = "mediatorErrorCode"
+import swiftPMImport.com.revenuecat.purchases.kn.core.RCAdTracker
+import swiftPMImport.com.revenuecat.purchases.kn.core.additional.AppleApiAvailability
@ExperimentalRevenueCatApi
@OptIn(ExperimentalForeignApi::class)
-public actual class AdTracker {
+public actual class AdTracker internal constructor(
+ private val iosAdTracker: RCAdTracker
+) {
public actual fun trackAdDisplayed(data: AdDisplayedData) {
- if (!IOSAPIAvailabilityChecker().isAdTrackingAPIAvailable()) {
+ if (!AppleApiAvailability().isAdTrackingAPIAvailable()) {
Purchases.logHandler.w("Purchases", "Ad tracking requires iOS 15.0+. Current API is unavailable.")
return
}
- val adData = buildMap {
- data.networkName?.let { put(KEY_NETWORK_NAME, it) }
- put(KEY_MEDIATOR_NAME, data.mediatorName.value)
- put(KEY_AD_FORMAT, data.adFormat.value)
- put(KEY_PLACEMENT, data.placement)
- put(KEY_AD_UNIT_ID, data.adUnitId)
- put(KEY_IMPRESSION_ID, data.impressionId)
- }
-
- RCCommonFunctionality.trackAdDisplayed(adData)
+ iosAdTracker.trackAdDisplayed(data.toIos())
}
public actual fun trackAdOpened(data: AdOpenedData) {
- if (!IOSAPIAvailabilityChecker().isAdTrackingAPIAvailable()) {
+ if (!AppleApiAvailability().isAdTrackingAPIAvailable()) {
Purchases.logHandler.w("Purchases", "Ad tracking requires iOS 15.0+. Current API is unavailable.")
return
}
- val adData = buildMap {
- data.networkName?.let { put(KEY_NETWORK_NAME, it) }
- put(KEY_MEDIATOR_NAME, data.mediatorName.value)
- put(KEY_AD_FORMAT, data.adFormat.value)
- put(KEY_PLACEMENT, data.placement)
- put(KEY_AD_UNIT_ID, data.adUnitId)
- put(KEY_IMPRESSION_ID, data.impressionId)
- }
-
- RCCommonFunctionality.trackAdOpened(adData)
+ iosAdTracker.trackAdOpened(data.toIos())
}
public actual fun trackAdRevenue(data: AdRevenueData) {
- if (!IOSAPIAvailabilityChecker().isAdTrackingAPIAvailable()) {
+ if (!AppleApiAvailability().isAdTrackingAPIAvailable()) {
Purchases.logHandler.w("Purchases", "Ad tracking requires iOS 15.0+. Current API is unavailable.")
return
}
- val adData = buildMap {
- data.networkName?.let { put(KEY_NETWORK_NAME, it) }
- put(KEY_MEDIATOR_NAME, data.mediatorName.value)
- put(KEY_AD_FORMAT, data.adFormat.value)
- put(KEY_PLACEMENT, data.placement)
- put(KEY_AD_UNIT_ID, data.adUnitId)
- put(KEY_IMPRESSION_ID, data.impressionId)
- put(KEY_REVENUE_MICROS, NSNumber(long = data.revenueMicros))
- put(KEY_CURRENCY, data.currency)
- put(KEY_PRECISION, data.precision.value)
- }
-
- RCCommonFunctionality.trackAdRevenue(adData)
+ iosAdTracker.trackAdRevenue(data.toIos())
}
public actual fun trackAdLoaded(data: AdLoadedData) {
- if (!IOSAPIAvailabilityChecker().isAdTrackingAPIAvailable()) {
+ if (!AppleApiAvailability().isAdTrackingAPIAvailable()) {
Purchases.logHandler.w("Purchases", "Ad tracking requires iOS 15.0+. Current API is unavailable.")
return
}
- val adData = buildMap {
- data.networkName?.let { put(KEY_NETWORK_NAME, it) }
- put(KEY_MEDIATOR_NAME, data.mediatorName.value)
- put(KEY_AD_FORMAT, data.adFormat.value)
- put(KEY_PLACEMENT, data.placement)
- put(KEY_AD_UNIT_ID, data.adUnitId)
- put(KEY_IMPRESSION_ID, data.impressionId)
- }
-
- RCCommonFunctionality.trackAdLoaded(adData)
+ iosAdTracker.trackAdLoaded(data.toIos())
}
public actual fun trackAdFailedToLoad(data: AdFailedToLoadData) {
- if (!IOSAPIAvailabilityChecker().isAdTrackingAPIAvailable()) {
+ if (!AppleApiAvailability().isAdTrackingAPIAvailable()) {
Purchases.logHandler.w("Purchases", "Ad tracking requires iOS 15.0+. Current API is unavailable.")
return
}
- val adData = buildMap {
- put(KEY_MEDIATOR_NAME, data.mediatorName.value)
- put(KEY_AD_FORMAT, data.adFormat.value)
- put(KEY_PLACEMENT, data.placement)
- put(KEY_AD_UNIT_ID, data.adUnitId)
- data.mediatorErrorCode?.let { put(KEY_MEDIATOR_ERROR_CODE, NSNumber(int = it)) }
- }
-
- RCCommonFunctionality.trackAdFailedToLoad(adData)
+ iosAdTracker.trackAdFailedToLoad(data.toIos())
}
}
diff --git a/core/src/iosMain/kotlin/com/revenuecat/purchases/kmp/Purchases.ios.kt b/core/src/iosMain/kotlin/com/revenuecat/purchases/kmp/Purchases.ios.kt
index 4988e3d2d..0c46114d9 100644
--- a/core/src/iosMain/kotlin/com/revenuecat/purchases/kmp/Purchases.ios.kt
+++ b/core/src/iosMain/kotlin/com/revenuecat/purchases/kmp/Purchases.ios.kt
@@ -1,77 +1,76 @@
package com.revenuecat.purchases.kmp
-import swiftPMImport.com.revenuecat.purchases.kn.core.IOSAPIAvailabilityChecker
-import swiftPMImport.com.revenuecat.purchases.kn.core.RCCommonFunctionality
-import swiftPMImport.com.revenuecat.purchases.kn.core.RCCustomerInfo
-import swiftPMImport.com.revenuecat.purchases.kn.core.RCIntroEligibility
-import swiftPMImport.com.revenuecat.purchases.kn.core.RCPurchaseParamsBuilder
-import swiftPMImport.com.revenuecat.purchases.kn.core.RCPurchasesDelegateProtocol
-import swiftPMImport.com.revenuecat.purchases.kn.core.RCStoreProduct
-import swiftPMImport.com.revenuecat.purchases.kn.core.RCStoreTransaction
-import swiftPMImport.com.revenuecat.purchases.kn.core.RCVirtualCurrencies
-import swiftPMImport.com.revenuecat.purchases.kn.core.configureWithAPIKey
-import swiftPMImport.com.revenuecat.purchases.kn.core.isWebPurchaseRedemptionURL
-import swiftPMImport.com.revenuecat.purchases.kn.core.parseAsWebPurchaseRedemptionWithUrlString
-import swiftPMImport.com.revenuecat.purchases.kn.core.recordPurchaseForProductID
-import swiftPMImport.com.revenuecat.purchases.kn.core.setAirbridgeDeviceID
-import swiftPMImport.com.revenuecat.purchases.kn.core.setAirshipChannelID
-import swiftPMImport.com.revenuecat.purchases.kn.core.setOnesignalUserID
-import swiftPMImport.com.revenuecat.purchases.kn.core.showStoreMessagesForTypes
-import swiftPMImport.com.revenuecat.purchases.kn.core.trackCustomPaywallImpression
-import com.revenuecat.purchases.kmp.models.CustomPaywallImpressionParams
-import com.revenuecat.purchases.kmp.ktx.mapEntriesNotNull
-import com.revenuecat.purchases.kmp.mappings.buildStoreTransaction
-import com.revenuecat.purchases.kmp.mappings.toCustomerInfo
-import com.revenuecat.purchases.kmp.mappings.toHybridString
-import com.revenuecat.purchases.kmp.mappings.toIntroEligibilityStatus
-import com.revenuecat.purchases.kmp.mappings.toIosCacheFetchPolicy
-import com.revenuecat.purchases.kmp.mappings.toIosPackage
-import com.revenuecat.purchases.kmp.mappings.toIosPromotionalOffer
-import com.revenuecat.purchases.kmp.mappings.toIosStoreProduct
-import com.revenuecat.purchases.kmp.mappings.toIosStoreProductDiscount
-import com.revenuecat.purchases.kmp.mappings.toIosWinBackOffer
-import com.revenuecat.purchases.kmp.mappings.toOfferings
-import com.revenuecat.purchases.kmp.mappings.toPromotionalOffer
-import com.revenuecat.purchases.kmp.mappings.toPurchasesErrorOrThrow
-import com.revenuecat.purchases.kmp.mappings.toStoreProduct
-import com.revenuecat.purchases.kmp.mappings.toStoreTransaction
-import com.revenuecat.purchases.kmp.mappings.toStorefront
-import com.revenuecat.purchases.kmp.mappings.toVirtualCurrencies
-import com.revenuecat.purchases.kmp.mappings.toWinBackOffer
-import com.revenuecat.purchases.kmp.models.AdDisplayedData
-import com.revenuecat.purchases.kmp.models.AdFailedToLoadData
-import com.revenuecat.purchases.kmp.models.AdLoadedData
-import com.revenuecat.purchases.kmp.models.AdOpenedData
-import com.revenuecat.purchases.kmp.models.AdRevenueData
-import com.revenuecat.purchases.kmp.models.BillingFeature
-import com.revenuecat.purchases.kmp.models.CacheFetchPolicy
-import com.revenuecat.purchases.kmp.models.CustomerInfo
-import com.revenuecat.purchases.kmp.models.DangerousSettings
-import com.revenuecat.purchases.kmp.models.IntroEligibilityStatus
-import com.revenuecat.purchases.kmp.models.Offerings
-import com.revenuecat.purchases.kmp.models.Package
-import com.revenuecat.purchases.kmp.models.PromotionalOffer
-import com.revenuecat.purchases.kmp.models.PurchasesError
-import com.revenuecat.purchases.kmp.models.PurchasesErrorCode
-import com.revenuecat.purchases.kmp.models.RedeemWebPurchaseListener
-import com.revenuecat.purchases.kmp.models.ReplacementMode
-import com.revenuecat.purchases.kmp.models.Store
-import com.revenuecat.purchases.kmp.models.StoreMessageType
-import com.revenuecat.purchases.kmp.models.StoreProduct
-import com.revenuecat.purchases.kmp.models.StoreProductDiscount
-import com.revenuecat.purchases.kmp.models.StoreTransaction
-import com.revenuecat.purchases.kmp.models.Storefront
-import com.revenuecat.purchases.kmp.models.SubscriptionOption
-import com.revenuecat.purchases.kmp.models.VirtualCurrencies
-import com.revenuecat.purchases.kmp.models.VirtualCurrency
-import com.revenuecat.purchases.kmp.models.WebPurchaseRedemption
-import com.revenuecat.purchases.kmp.models.WinBackOffer
-import com.revenuecat.purchases.kmp.strings.ConfigureStrings
-import platform.Foundation.NSError
-import platform.Foundation.NSURL
-import swiftPMImport.com.revenuecat.purchases.kn.core.RCDangerousSettings as IosDangerousSettings
-import swiftPMImport.com.revenuecat.purchases.kn.core.RCPurchases as IosPurchases
-import swiftPMImport.com.revenuecat.purchases.kn.core.RCWinBackOffer as NativeIosWinBackOffer
+ import com.revenuecat.purchases.kmp.ktx.mapEntriesNotNull
+ import com.revenuecat.purchases.kmp.mappings.toCustomerInfo
+ import com.revenuecat.purchases.kmp.mappings.toIntroEligibilityStatus
+ import com.revenuecat.purchases.kmp.mappings.toIosCacheFetchPolicy
+ import com.revenuecat.purchases.kmp.mappings.toIosEntitlementVerificationMode
+ import com.revenuecat.purchases.kmp.mappings.toIosPackage
+ import com.revenuecat.purchases.kmp.mappings.toIosPromotionalOffer
+ import com.revenuecat.purchases.kmp.mappings.toIosPurchasesAreCompletedBy
+ import com.revenuecat.purchases.kmp.mappings.toIosStoreKitVersion
+ import com.revenuecat.purchases.kmp.mappings.toIosStoreMessageTypes
+ import com.revenuecat.purchases.kmp.mappings.toIosStoreProduct
+ import com.revenuecat.purchases.kmp.mappings.toIosStoreProductDiscount
+ import com.revenuecat.purchases.kmp.mappings.toIosWinBackOffer
+ import com.revenuecat.purchases.kmp.mappings.toOfferings
+ import com.revenuecat.purchases.kmp.mappings.toPromotionalOffer
+ import com.revenuecat.purchases.kmp.mappings.toPurchasesErrorOrThrow
+ import com.revenuecat.purchases.kmp.mappings.toStoreProduct
+ import com.revenuecat.purchases.kmp.mappings.toStoreTransaction
+ import com.revenuecat.purchases.kmp.mappings.toStorefront
+ import com.revenuecat.purchases.kmp.mappings.toVirtualCurrencies
+ import com.revenuecat.purchases.kmp.mappings.toWinBackOffer
+ import com.revenuecat.purchases.kmp.models.BillingFeature
+ import com.revenuecat.purchases.kmp.models.CacheFetchPolicy
+ import com.revenuecat.purchases.kmp.models.CustomPaywallImpressionParams
+ import com.revenuecat.purchases.kmp.models.CustomerInfo
+ import com.revenuecat.purchases.kmp.models.DangerousSettings
+ import com.revenuecat.purchases.kmp.models.IntroEligibilityStatus
+ import com.revenuecat.purchases.kmp.models.Offerings
+ import com.revenuecat.purchases.kmp.models.Package
+ import com.revenuecat.purchases.kmp.models.PromotionalOffer
+ import com.revenuecat.purchases.kmp.models.PurchasesError
+ import com.revenuecat.purchases.kmp.models.PurchasesErrorCode
+ import com.revenuecat.purchases.kmp.models.RedeemWebPurchaseListener
+ import com.revenuecat.purchases.kmp.models.ReplacementMode
+ import com.revenuecat.purchases.kmp.models.Store
+ import com.revenuecat.purchases.kmp.models.StoreMessageType
+ import com.revenuecat.purchases.kmp.models.StoreProduct
+ import com.revenuecat.purchases.kmp.models.StoreProductDiscount
+ import com.revenuecat.purchases.kmp.models.StoreTransaction
+ import com.revenuecat.purchases.kmp.models.Storefront
+ import com.revenuecat.purchases.kmp.models.SubscriptionOption
+ import com.revenuecat.purchases.kmp.models.VirtualCurrencies
+ import com.revenuecat.purchases.kmp.models.WebPurchaseRedemption
+ import com.revenuecat.purchases.kmp.models.WinBackOffer
+ import com.revenuecat.purchases.kmp.strings.ConfigureStrings
+ import platform.Foundation.NSError
+ import platform.Foundation.NSURL
+ import platform.Foundation.NSUserDefaults
+ import swiftPMImport.com.revenuecat.purchases.kn.core.RCConfiguration
+ import swiftPMImport.com.revenuecat.purchases.kn.core.RCCustomPaywallImpressionParams
+ import swiftPMImport.com.revenuecat.purchases.kn.core.RCCustomerInfo
+ import swiftPMImport.com.revenuecat.purchases.kn.core.RCIntroEligibility
+ import swiftPMImport.com.revenuecat.purchases.kn.core.RCPlatformInfo
+ import swiftPMImport.com.revenuecat.purchases.kn.core.RCPurchaseParamsBuilder
+ import swiftPMImport.com.revenuecat.purchases.kn.core.RCPurchasesDelegateProtocol
+ import swiftPMImport.com.revenuecat.purchases.kn.core.RCStoreProduct
+ import swiftPMImport.com.revenuecat.purchases.kn.core.RCStoreTransaction
+ import swiftPMImport.com.revenuecat.purchases.kn.core.RCVirtualCurrencies
+ import swiftPMImport.com.revenuecat.purchases.kn.core.additional.AppleApiAvailability
+ import swiftPMImport.com.revenuecat.purchases.kn.core.configureWithConfiguration
+ import swiftPMImport.com.revenuecat.purchases.kn.core.enableAdServicesAttributionTokenCollection
+ import swiftPMImport.com.revenuecat.purchases.kn.core.parseAsWebPurchaseRedemption
+ import swiftPMImport.com.revenuecat.purchases.kn.core.recordPurchaseForProductID
+ import swiftPMImport.com.revenuecat.purchases.kn.core.setAirbridgeDeviceID
+ import swiftPMImport.com.revenuecat.purchases.kn.core.setAirshipChannelID
+ import swiftPMImport.com.revenuecat.purchases.kn.core.setOnesignalUserID
+ import swiftPMImport.com.revenuecat.purchases.kn.core.showStoreMessagesForTypes
+ import swiftPMImport.com.revenuecat.purchases.kn.core.trackCustomPaywallImpression
+ import swiftPMImport.com.revenuecat.purchases.kn.core.RCDangerousSettings as IosDangerousSettings
+ import swiftPMImport.com.revenuecat.purchases.kn.core.RCPurchases as IosPurchases
+ import swiftPMImport.com.revenuecat.purchases.kn.core.RCWinBackOffer as NativeIosWinBackOffer
public actual class Purchases private constructor(private val iosPurchases: IosPurchases) {
public actual companion object {
@@ -106,25 +105,33 @@ public actual class Purchases private constructor(private val iosPurchases: IosP
public actual fun configure(
configuration: PurchasesConfiguration
- ): Purchases {
- checkCommonVersion()
-
- return with(configuration) {
- IosPurchases.configureWithAPIKey(
- apiKey = apiKey,
- appUserID = appUserId,
- purchasesAreCompletedBy = purchasesAreCompletedBy.toHybridString(),
- userDefaultsSuiteName = userDefaultsSuiteName,
- platformFlavor = BuildKonfig.platformFlavor,
- platformFlavorVersion = frameworkVersion,
- storeKitVersion = storeKitVersion.toHybridString(),
- dangerousSettings = dangerousSettings.toIosDangerousSettings(),
- shouldShowInAppMessagesAutomatically = showInAppMessagesAutomatically,
- verificationMode = verificationMode.name,
- )
- }.let { Purchases(it) }
- .also { _sharedInstance = it }
- }
+ ): Purchases = with(configuration) {
+ IosPurchases.configureWithConfiguration(
+ configuration = RCConfiguration.builderWithAPIKey(apiKey)
+ .withAppUserID(appUserId)
+ .apply {
+ purchasesAreCompletedBy.toIosPurchasesAreCompletedBy().also {
+ withPurchasesAreCompletedBy(
+ purchasesAreCompletedBy = it.purchasesAreCompletedBy,
+ storeKitVersion = it.storeKitVersion
+ )
+ }
+ }
+ .withUserDefaults(NSUserDefaults(suiteName = userDefaultsSuiteName))
+ .withPlatformInfo(
+ RCPlatformInfo(
+ flavor = BuildKonfig.platformFlavor,
+ version = frameworkVersion
+ )
+ )
+ .withStoreKitVersion(storeKitVersion.toIosStoreKitVersion())
+ .withDangerousSettings(dangerousSettings.toIosDangerousSettings())
+ .withShowStoreMessagesAutomatically(showInAppMessagesAutomatically)
+ .withEntitlementVerificationMode(verificationMode.toIosEntitlementVerificationMode())
+ .build()
+ )
+ }.let { Purchases(it) }
+ .also { _sharedInstance = it }
public actual fun canMakePayments(
features: List,
@@ -133,20 +140,11 @@ public actual class Purchases private constructor(private val iosPurchases: IosP
callback(IosPurchases.canMakePayments())
}
- private fun checkCommonVersion() {
- val expected = BuildKonfig.revenuecatCommonVersion
- val actual = RCCommonFunctionality.hybridCommonVersion()
- check(actual == expected) {
- "Unexpected version of PurchasesHybridCommon ('$actual'). Make sure to use " +
- "'$expected' exactly."
- }
- }
-
private fun DangerousSettings.toIosDangerousSettings(): IosDangerousSettings =
IosDangerousSettings(autoSyncPurchases)
public actual fun parseAsWebPurchaseRedemption(url: String): WebPurchaseRedemption? {
- return if (RCCommonFunctionality.isWebPurchaseRedemptionURL(url)) {
+ return if (IosPurchases.parseAsWebPurchaseRedemption(NSURL(string = url)) != null) {
WebPurchaseRedemption(url)
} else {
null
@@ -342,40 +340,23 @@ public actual class Purchases private constructor(private val iosPurchases: IosP
onError: (error: PurchasesError) -> Unit,
onSuccess: (storeTransaction: StoreTransaction) -> Unit,
) {
- RCCommonFunctionality.recordPurchaseForProductID(
- productID,
- completion = { storeTransactionMap, error ->
- if (error != null) {
- onError(error.error().toPurchasesErrorOrThrow())
- return@recordPurchaseForProductID
- }
-
- if (storeTransactionMap == null) {
- onError(
- PurchasesError(
- code = PurchasesErrorCode.UnknownError,
- underlyingErrorMessage =
- "Expected storeTransactionMap to be non-null when error is non-null."
- )
- )
- } else {
- val storeTransactionMappingResult = buildStoreTransaction(
- storeTransactionMap = storeTransactionMap
+ iosPurchases.recordPurchaseForProductID(productID) { storeTransaction, error ->
+ if (error != null) {
+ onError(error.toPurchasesErrorOrThrow())
+ return@recordPurchaseForProductID
+ }
+ if (storeTransaction == null) {
+ onError(
+ PurchasesError(
+ code = PurchasesErrorCode.UnknownError,
+ underlyingErrorMessage =
+ "Expected a non-null RCStoreTransaction when error is non-null."
)
- storeTransactionMappingResult.onSuccess {
- onSuccess(it)
- }
- storeTransactionMappingResult.onFailure {
- onError(
- PurchasesError(
- code = PurchasesErrorCode.UnknownError,
- underlyingErrorMessage = it.message
- )
- )
- }
- }
+ )
+ } else {
+ onSuccess(storeTransaction.toStoreTransaction())
}
- )
+ }
}
public actual fun checkTrialOrIntroPriceEligibility(
@@ -403,7 +384,7 @@ public actual class Purchases private constructor(private val iosPurchases: IosP
// API availability checks must be performed here at the KMP level, since the KMP/ObjC/Swift
// interoperability drops the @available(osVersion) requirements, and you can technically
// call functions with an @available from any OS version in KMP
- if (!IOSAPIAvailabilityChecker().isWinBackOfferAPIAvailable()) {
+ if (!AppleApiAvailability().isWinBackOfferAPIAvailable()) {
onError(
PurchasesError(
PurchasesErrorCode.UnsupportedError,
@@ -449,7 +430,7 @@ public actual class Purchases private constructor(private val iosPurchases: IosP
// API availability checks must be performed here at the KMP level, since the KMP/ObjC/Swift
// interoperability drops the @available(osVersion) requirements, and you can technically
// call functions with an @available from any OS version in KMP
- if (!IOSAPIAvailabilityChecker().isWinBackOfferAPIAvailable()) {
+ if (!AppleApiAvailability().isWinBackOfferAPIAvailable()) {
onError(
PurchasesError(
PurchasesErrorCode.UnsupportedError,
@@ -496,7 +477,7 @@ public actual class Purchases private constructor(private val iosPurchases: IosP
// API availability checks must be performed here at the KMP level, since the KMP/ObjC/Swift
// interoperability drops the @available(osVersion) requirements, and you can technically
// call functions with an @available from any OS version in KMP
- if (!IOSAPIAvailabilityChecker().isWinBackOfferAPIAvailable()) {
+ if (!AppleApiAvailability().isWinBackOfferAPIAvailable()) {
onError(
PurchasesError(
PurchasesErrorCode.UnsupportedError,
@@ -553,7 +534,7 @@ public actual class Purchases private constructor(private val iosPurchases: IosP
// API availability checks must be performed here at the KMP level, since the KMP/ObjC/Swift
// interoperability drops the @available(osVersion) requirements, and you can technically
// call functions with an @available from any OS version in KMP
- if (!IOSAPIAvailabilityChecker().isWinBackOfferAPIAvailable()) {
+ if (!AppleApiAvailability().isWinBackOfferAPIAvailable()) {
onError(
PurchasesError(
PurchasesErrorCode.UnsupportedError,
@@ -638,17 +619,9 @@ public actual class Purchases private constructor(private val iosPurchases: IosP
public actual fun showInAppMessagesIfNeeded(
messageTypes: List,
- ): Unit = RCCommonFunctionality.showStoreMessagesForTypes(
- rawValues = messageTypes.mapTo(mutableSetOf()) { type ->
- when (type) {
- StoreMessageType.BILLING_ISSUES -> 0
- StoreMessageType.PRICE_INCREASE_CONSENT -> 1
- StoreMessageType.GENERIC -> 2
- StoreMessageType.WIN_BACK_OFFER -> 3
- }
- },
- completion = { }
- )
+ ) {
+ iosPurchases.showStoreMessagesForTypes(messageTypes.toIosStoreMessageTypes()) {}
+ }
public actual fun invalidateCustomerInfoCache(): Unit =
iosPurchases.invalidateCustomerInfoCache()
@@ -675,10 +648,10 @@ public actual class Purchases private constructor(private val iosPurchases: IosP
iosPurchases.setOnesignalID(onesignalID)
public actual fun setOnesignalUserID(onesignalUserID: String?): Unit =
- RCCommonFunctionality.setOnesignalUserID(onesignalUserID)
+ iosPurchases.attribution().setOnesignalUserID(onesignalUserID)
public actual fun setAirshipChannelID(airshipChannelID: String?): Unit =
- RCCommonFunctionality.setAirshipChannelID(airshipChannelID)
+ iosPurchases.attribution().setAirshipChannelID(airshipChannelID)
public actual fun setFirebaseAppInstanceID(firebaseAppInstanceID: String?): Unit =
iosPurchases.setFirebaseAppInstanceID(firebaseAppInstanceID)
@@ -693,7 +666,7 @@ public actual class Purchases private constructor(private val iosPurchases: IosP
iosPurchases.setAppsflyerID(appsflyerID)
public actual fun setAirbridgeDeviceID(airbridgeDeviceID: String?): Unit =
- RCCommonFunctionality.setAirbridgeDeviceID(airbridgeDeviceID)
+ iosPurchases.attribution().setAirbridgeDeviceID(airbridgeDeviceID)
public actual fun setFBAnonymousID(fbAnonymousID: String?): Unit =
iosPurchases.setFBAnonymousID(fbAnonymousID)
@@ -723,8 +696,8 @@ public actual class Purchases private constructor(private val iosPurchases: IosP
iosPurchases.setCreative(creative)
public actual fun presentCodeRedemptionSheet() {
- if (IOSAPIAvailabilityChecker().isCodeRedemptionSheetAPIAvailable())
- RCCommonFunctionality.presentCodeRedemptionSheet()
+ if (AppleApiAvailability().isCodeRedemptionSheetAPIAvailable())
+ iosPurchases.presentCodeRedemptionSheet()
else logHandler.d(
tag = "Purchases",
msg = "`presentCodeRedemptionSheet()` is only available on iOS 14.0 and up."
@@ -732,8 +705,8 @@ public actual class Purchases private constructor(private val iosPurchases: IosP
}
public actual fun enableAdServicesAttributionTokenCollection() {
- if (IOSAPIAvailabilityChecker().isEnableAdServicesAttributionTokenCollectionAPIAvailable())
- RCCommonFunctionality.enableAdServicesAttributionTokenCollection()
+ if (AppleApiAvailability().isEnableAdServicesAttributionTokenCollectionAPIAvailable())
+ iosPurchases.attribution().enableAdServicesAttributionTokenCollection()
else logHandler.d(
tag = "Purchases",
msg = "`enableAdServicesAttributionTokenCollection()` is only available on iOS 14.3 " +
@@ -745,8 +718,8 @@ public actual class Purchases private constructor(private val iosPurchases: IosP
webPurchaseRedemption: WebPurchaseRedemption,
listener: RedeemWebPurchaseListener,
) {
- val nativeWebPurchaseRedemption = RCCommonFunctionality.parseAsWebPurchaseRedemptionWithUrlString(
- webPurchaseRedemption.redemptionUrl,
+ val nativeWebPurchaseRedemption = IosPurchases.parseAsWebPurchaseRedemption(
+ url = NSURL(string = webPurchaseRedemption.redemptionUrl)
)
if (nativeWebPurchaseRedemption == null) {
listener.handleResult(RedeemWebPurchaseListener.Result.Error(
@@ -814,19 +787,21 @@ public actual class Purchases private constructor(private val iosPurchases: IosP
public actual fun trackCustomPaywallImpression(
params: CustomPaywallImpressionParams,
) {
- if (!IOSAPIAvailabilityChecker().isCustomPaywallTrackingAPIAvailable()) {
- Purchases.logHandler.w(
+ if (!AppleApiAvailability().isCustomPaywallTrackingAPIAvailable()) {
+ logHandler.w(
"Purchases",
"Custom paywall tracking requires iOS 15.0+. Current API is unavailable."
)
return
}
- val data = mutableMapOf()
- params.paywallId?.let { data["paywallId"] = it }
- params.offeringId?.let { data["offeringId"] = it }
- RCCommonFunctionality.trackCustomPaywallImpression(data)
+ iosPurchases.trackCustomPaywallImpression(
+ RCCustomPaywallImpressionParams(
+ paywallId = params.paywallId,
+ offeringId = params.offeringId
+ )
+ )
}
@ExperimentalRevenueCatApi
- public actual val adTracker: AdTracker by lazy { AdTracker() }
+ public actual val adTracker: AdTracker by lazy { AdTracker(iosPurchases.adTracker()) }
}
diff --git a/core/src/commonTest/kotlin/com/revenuecat/purchases/kmp/PrintLnLogHandler.kt b/core/src/iosTest/kotlin/com/revenuecat/purchases/kmp/PrintLnLogHandler.kt
similarity index 100%
rename from core/src/commonTest/kotlin/com/revenuecat/purchases/kmp/PrintLnLogHandler.kt
rename to core/src/iosTest/kotlin/com/revenuecat/purchases/kmp/PrintLnLogHandler.kt
diff --git a/datetime/.gitignore b/datetime/.gitignore
deleted file mode 100644
index 42afabfd2..000000000
--- a/datetime/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/build
\ No newline at end of file
diff --git a/datetime/api/datetime.api b/datetime/api/datetime.api
deleted file mode 100644
index da12561c1..000000000
--- a/datetime/api/datetime.api
+++ /dev/null
@@ -1,21 +0,0 @@
-public final class com/revenuecat/purchases/kmp/datetime/CustomerInfoKt {
- public static final fun getAllExpirationInstants (Lcom/revenuecat/purchases/kmp/models/CustomerInfo;)Ljava/util/Map;
- public static final fun getAllPurchaseInstants (Lcom/revenuecat/purchases/kmp/models/CustomerInfo;)Ljava/util/Map;
- public static final fun getFirstSeenInstant (Lcom/revenuecat/purchases/kmp/models/CustomerInfo;)Lkotlinx/datetime/Instant;
- public static final fun getLatestExpirationInstant (Lcom/revenuecat/purchases/kmp/models/CustomerInfo;)Lkotlinx/datetime/Instant;
- public static final fun getOriginalPurchaseInstant (Lcom/revenuecat/purchases/kmp/models/CustomerInfo;)Lkotlinx/datetime/Instant;
- public static final fun getRequestInstant (Lcom/revenuecat/purchases/kmp/models/CustomerInfo;)Lkotlinx/datetime/Instant;
-}
-
-public final class com/revenuecat/purchases/kmp/datetime/EntitlementInfoKt {
- public static final fun getBillingIssueDetectedAtInstant (Lcom/revenuecat/purchases/kmp/models/EntitlementInfo;)Lkotlinx/datetime/Instant;
- public static final fun getExpirationInstant (Lcom/revenuecat/purchases/kmp/models/EntitlementInfo;)Lkotlinx/datetime/Instant;
- public static final fun getLatestPurchaseInstant (Lcom/revenuecat/purchases/kmp/models/EntitlementInfo;)Lkotlinx/datetime/Instant;
- public static final fun getOriginalPurchaseInstant (Lcom/revenuecat/purchases/kmp/models/EntitlementInfo;)Lkotlinx/datetime/Instant;
- public static final fun getUnsubscribeDetectedAtInstant (Lcom/revenuecat/purchases/kmp/models/EntitlementInfo;)Lkotlinx/datetime/Instant;
-}
-
-public final class com/revenuecat/purchases/kmp/datetime/TransactionKt {
- public static final fun getPurchaseInstant (Lcom/revenuecat/purchases/kmp/models/Transaction;)Lkotlinx/datetime/Instant;
-}
-
diff --git a/datetime/api/datetime.klib.api b/datetime/api/datetime.klib.api
deleted file mode 100644
index d3565bcfa..000000000
--- a/datetime/api/datetime.klib.api
+++ /dev/null
@@ -1,32 +0,0 @@
-// Klib ABI Dump
-// Targets: [iosArm64, iosSimulatorArm64, iosX64]
-// Rendering settings:
-// - Signature version: 2
-// - Show manifest properties: true
-// - Show declarations: true
-
-// Library unique name:
-final val com.revenuecat.purchases.kmp.datetime/allExpirationInstants // com.revenuecat.purchases.kmp.datetime/allExpirationInstants|@com.revenuecat.purchases.kmp.models.CustomerInfo{}allExpirationInstants[0]
- final fun (com.revenuecat.purchases.kmp.models/CustomerInfo).(): kotlin.collections/Map // com.revenuecat.purchases.kmp.datetime/allExpirationInstants.|@com.revenuecat.purchases.kmp.models.CustomerInfo(){}[0]
-final val com.revenuecat.purchases.kmp.datetime/allPurchaseInstants // com.revenuecat.purchases.kmp.datetime/allPurchaseInstants|@com.revenuecat.purchases.kmp.models.CustomerInfo{}allPurchaseInstants[0]
- final fun (com.revenuecat.purchases.kmp.models/CustomerInfo).(): kotlin.collections/Map // com.revenuecat.purchases.kmp.datetime/allPurchaseInstants.|@com.revenuecat.purchases.kmp.models.CustomerInfo(){}[0]
-final val com.revenuecat.purchases.kmp.datetime/billingIssueDetectedAtInstant // com.revenuecat.purchases.kmp.datetime/billingIssueDetectedAtInstant|@com.revenuecat.purchases.kmp.models.EntitlementInfo{}billingIssueDetectedAtInstant[0]
- final fun (com.revenuecat.purchases.kmp.models/EntitlementInfo).(): kotlinx.datetime/Instant? // com.revenuecat.purchases.kmp.datetime/billingIssueDetectedAtInstant.|@com.revenuecat.purchases.kmp.models.EntitlementInfo(){}[0]
-final val com.revenuecat.purchases.kmp.datetime/expirationInstant // com.revenuecat.purchases.kmp.datetime/expirationInstant|@com.revenuecat.purchases.kmp.models.EntitlementInfo{}expirationInstant[0]
- final fun (com.revenuecat.purchases.kmp.models/EntitlementInfo).(): kotlinx.datetime/Instant? // com.revenuecat.purchases.kmp.datetime/expirationInstant.|@com.revenuecat.purchases.kmp.models.EntitlementInfo(){}[0]
-final val com.revenuecat.purchases.kmp.datetime/firstSeenInstant // com.revenuecat.purchases.kmp.datetime/firstSeenInstant|@com.revenuecat.purchases.kmp.models.CustomerInfo{}firstSeenInstant[0]
- final fun (com.revenuecat.purchases.kmp.models/CustomerInfo).(): kotlinx.datetime/Instant // com.revenuecat.purchases.kmp.datetime/firstSeenInstant.|@com.revenuecat.purchases.kmp.models.CustomerInfo(){}[0]
-final val com.revenuecat.purchases.kmp.datetime/latestExpirationInstant // com.revenuecat.purchases.kmp.datetime/latestExpirationInstant|@com.revenuecat.purchases.kmp.models.CustomerInfo{}latestExpirationInstant[0]
- final fun (com.revenuecat.purchases.kmp.models/CustomerInfo).(): kotlinx.datetime/Instant? // com.revenuecat.purchases.kmp.datetime/latestExpirationInstant.|@com.revenuecat.purchases.kmp.models.CustomerInfo(){}[0]
-final val com.revenuecat.purchases.kmp.datetime/latestPurchaseInstant // com.revenuecat.purchases.kmp.datetime/latestPurchaseInstant|@com.revenuecat.purchases.kmp.models.EntitlementInfo{}latestPurchaseInstant[0]
- final fun (com.revenuecat.purchases.kmp.models/EntitlementInfo).(): kotlinx.datetime/Instant? // com.revenuecat.purchases.kmp.datetime/latestPurchaseInstant.|@com.revenuecat.purchases.kmp.models.EntitlementInfo(){}[0]
-final val com.revenuecat.purchases.kmp.datetime/originalPurchaseInstant // com.revenuecat.purchases.kmp.datetime/originalPurchaseInstant|@com.revenuecat.purchases.kmp.models.CustomerInfo{}originalPurchaseInstant[0]
- final fun (com.revenuecat.purchases.kmp.models/CustomerInfo).(): kotlinx.datetime/Instant? // com.revenuecat.purchases.kmp.datetime/originalPurchaseInstant.|@com.revenuecat.purchases.kmp.models.CustomerInfo(){}[0]
-final val com.revenuecat.purchases.kmp.datetime/originalPurchaseInstant // com.revenuecat.purchases.kmp.datetime/originalPurchaseInstant|@com.revenuecat.purchases.kmp.models.EntitlementInfo{}originalPurchaseInstant[0]
- final fun (com.revenuecat.purchases.kmp.models/EntitlementInfo).(): kotlinx.datetime/Instant? // com.revenuecat.purchases.kmp.datetime/originalPurchaseInstant.|@com.revenuecat.purchases.kmp.models.EntitlementInfo(){}[0]
-final val com.revenuecat.purchases.kmp.datetime/purchaseInstant // com.revenuecat.purchases.kmp.datetime/purchaseInstant|@com.revenuecat.purchases.kmp.models.Transaction{}purchaseInstant[0]
- final fun (com.revenuecat.purchases.kmp.models/Transaction).(): kotlinx.datetime/Instant // com.revenuecat.purchases.kmp.datetime/purchaseInstant.|@com.revenuecat.purchases.kmp.models.Transaction(){}[0]
-final val com.revenuecat.purchases.kmp.datetime/requestInstant // com.revenuecat.purchases.kmp.datetime/requestInstant|@com.revenuecat.purchases.kmp.models.CustomerInfo{}requestInstant[0]
- final fun (com.revenuecat.purchases.kmp.models/CustomerInfo).(): kotlinx.datetime/Instant // com.revenuecat.purchases.kmp.datetime/requestInstant.|@com.revenuecat.purchases.kmp.models.CustomerInfo(){}[0]
-final val com.revenuecat.purchases.kmp.datetime/unsubscribeDetectedAtInstant // com.revenuecat.purchases.kmp.datetime/unsubscribeDetectedAtInstant|@com.revenuecat.purchases.kmp.models.EntitlementInfo{}unsubscribeDetectedAtInstant[0]
- final fun (com.revenuecat.purchases.kmp.models/EntitlementInfo).(): kotlinx.datetime/Instant? // com.revenuecat.purchases.kmp.datetime/unsubscribeDetectedAtInstant.|@com.revenuecat.purchases.kmp.models.EntitlementInfo(){}[0]
diff --git a/datetime/build.gradle.kts b/datetime/build.gradle.kts
deleted file mode 100644
index 483c99941..000000000
--- a/datetime/build.gradle.kts
+++ /dev/null
@@ -1,20 +0,0 @@
-plugins {
- id("revenuecat-library")
-}
-
-revenueCat {
- dokka = true
-}
-
-kotlin {
- sourceSets {
- commonMain.dependencies {
- api(libs.kotlinx.datetime)
- implementation(projects.core)
- }
- }
-}
-
-android {
- namespace = "com.revenuecat.purchases.kmp.datetime"
-}
diff --git a/datetime/src/commonMain/kotlin/com/revenuecat/purchases/kmp/datetime/CustomerInfo.kt b/datetime/src/commonMain/kotlin/com/revenuecat/purchases/kmp/datetime/CustomerInfo.kt
deleted file mode 100644
index 40ae328c5..000000000
--- a/datetime/src/commonMain/kotlin/com/revenuecat/purchases/kmp/datetime/CustomerInfo.kt
+++ /dev/null
@@ -1,88 +0,0 @@
-package com.revenuecat.purchases.kmp.datetime
-
-import com.revenuecat.purchases.kmp.Purchases
-import com.revenuecat.purchases.kmp.models.CustomerInfo
-import kotlinx.datetime.Instant
-
-/**
- * Map of productIds to expiration instants.
- * * For Google subscriptions, productIds are `subscriptionId:basePlanId`.
- * * For Amazon subscriptions, productsIds are `termSku`.
- */
-@Deprecated(
- message = "Instant properties have been moved to member fields. The entire " +
- "purchases-kmp-datetime module is deprecated and can be removed from your Gradle " +
- "files.",
- replaceWith = ReplaceWith("allExpirationDates"),
-)
-public val CustomerInfo.allExpirationInstants: Map
- get() = allExpirationDateMillis.mapValues { (_, millis) ->
- millis?.let { Instant.fromEpochMilliseconds(it) }
- }
-
-/**
- * Map of productIds to purchase instants.
- * * For Google subscriptions, productIds are `subscriptionId:basePlanId`.
- * * For Google and Amazon INAPPs, productsIds are simply `productId`.
- * * For Amazon subscriptions, productsIds are `termSku`.
- */
-@Deprecated(
- message = "Instant properties have been moved to member fields. The entire " +
- "purchases-kmp-datetime module is deprecated and can be removed from your Gradle " +
- "files.",
- replaceWith = ReplaceWith("allPurchaseDates"),
-)
-public val CustomerInfo.allPurchaseInstants: Map
- get() = allPurchaseDateMillis.mapValues { (_, millis) ->
- millis?.let { Instant.fromEpochMilliseconds(it) }
- }
-
-/**
- * The instant this user was first seen in RevenueCat.
- */
-@Deprecated(
- message = "Instant properties have been moved to member fields. The entire " +
- "purchases-kmp-datetime module is deprecated and can be removed from your Gradle " +
- "files.",
- replaceWith = ReplaceWith("firstSeen"),
-)
-public val CustomerInfo.firstSeenInstant: Instant
- get() = Instant.fromEpochMilliseconds(firstSeenMillis)
-
-/**
- * The latest expiration instant of all purchased productIds.
- */
-@Deprecated(
- message = "Instant properties have been moved to member fields. The entire " +
- "purchases-kmp-datetime module is deprecated and can be removed from your Gradle " +
- "files.",
- replaceWith = ReplaceWith("latestExpirationDate"),
-)
-public val CustomerInfo.latestExpirationInstant: Instant?
- get() = latestExpirationDateMillis?.let { Instant.fromEpochMilliseconds(it) }
-
-/**
- * The purchase instant for the version of the application when the user bought the app. Use this
- * for grandfathering users when migrating to subscriptions. This can be null, see
- * [Purchases.restorePurchases].
- */
-@Deprecated(
- message = "Instant properties have been moved to member fields. The entire " +
- "purchases-kmp-datetime module is deprecated and can be removed from your Gradle " +
- "files.",
- replaceWith = ReplaceWith("originalPurchaseDate"),
-)
-public val CustomerInfo.originalPurchaseInstant: Instant?
- get() = originalPurchaseDateMillis?.let { Instant.fromEpochMilliseconds(it) }
-
-/**
- * The instant when this info was requested.
- */
-@Deprecated(
- message = "Instant properties have been moved to member fields. The entire " +
- "purchases-kmp-datetime module is deprecated and can be removed from your Gradle " +
- "files.",
- replaceWith = ReplaceWith("requestDate"),
-)
-public val CustomerInfo.requestInstant: Instant
- get() = Instant.fromEpochMilliseconds(requestDateMillis)
diff --git a/datetime/src/commonMain/kotlin/com/revenuecat/purchases/kmp/datetime/EntitlementInfo.kt b/datetime/src/commonMain/kotlin/com/revenuecat/purchases/kmp/datetime/EntitlementInfo.kt
deleted file mode 100644
index eb5c707f8..000000000
--- a/datetime/src/commonMain/kotlin/com/revenuecat/purchases/kmp/datetime/EntitlementInfo.kt
+++ /dev/null
@@ -1,72 +0,0 @@
-package com.revenuecat.purchases.kmp.datetime
-
-import com.revenuecat.purchases.kmp.models.EntitlementInfo
-import com.revenuecat.purchases.kmp.models.PeriodType
-import kotlinx.datetime.Instant
-
-
-/**
- * Nullable on iOS only not on Android. The latest purchase or renewal instant for the entitlement.
- */
-@Deprecated(
- message = "Instant properties have been moved to member fields. The entire " +
- "purchases-kmp-datetime module is deprecated and can be removed from your Gradle " +
- "files.",
- replaceWith = ReplaceWith("latestPurchaseDate"),
-)
-public val EntitlementInfo.latestPurchaseInstant: Instant?
- get() = latestPurchaseDateMillis?.let { Instant.fromEpochMilliseconds(it) }
-
-/**
- * Nullable on iOS only not on Android. The first instant this entitlement was purchased.
- */
-@Deprecated(
- message = "Instant properties have been moved to member fields. The entire " +
- "purchases-kmp-datetime module is deprecated and can be removed from your Gradle " +
- "files.",
- replaceWith = ReplaceWith("originalPurchaseDate"),
-)
-public val EntitlementInfo.originalPurchaseInstant: Instant?
- get() = originalPurchaseDateMillis?.let { Instant.fromEpochMilliseconds(it) }
-
-/**
- * The expiration instant for the entitlement, can be `null` for lifetime access. If the
- * [periodType] is [PeriodType.TRIAL], this is the trial expiration instant.
- */
-@Deprecated(
- message = "Instant properties have been moved to member fields. The entire " +
- "purchases-kmp-datetime module is deprecated and can be removed from your Gradle " +
- "files.",
- replaceWith = ReplaceWith("expirationDate"),
-)
-public val EntitlementInfo.expirationInstant: Instant?
- get() = expirationDateMillis?.let { Instant.fromEpochMilliseconds(it) }
-
-/**
- * The instant an unsubscribe was detected. Can be `null`.
- *
- * Note: Entitlement may still be active even if user has unsubscribed. Check the [isActive]
- * property.
- */
-@Deprecated(
- message = "Instant properties have been moved to member fields. The entire " +
- "purchases-kmp-datetime module is deprecated and can be removed from your Gradle " +
- "files.",
- replaceWith = ReplaceWith("unsubscribeDetectedAt"),
-)
-public val EntitlementInfo.unsubscribeDetectedAtInstant: Instant?
- get() = unsubscribeDetectedAtMillis?.let { Instant.fromEpochMilliseconds(it) }
-
-/**
- * The instant a billing issue was detected. Can be `null` if there is no billing issue or an issue
- * has been resolved. Note: Entitlement may still be active even if there is a billing issue.
- * Check the [isActive] property.
- */
-@Deprecated(
- message = "Instant properties have been moved to member fields. The entire " +
- "purchases-kmp-datetime module is deprecated and can be removed from your Gradle " +
- "files.",
- replaceWith = ReplaceWith("billingIssueDetectedAt"),
-)
-public val EntitlementInfo.billingIssueDetectedAtInstant: Instant?
- get() = billingIssueDetectedAtMillis?.let { Instant.fromEpochMilliseconds(it) }
diff --git a/datetime/src/commonMain/kotlin/com/revenuecat/purchases/kmp/datetime/Transaction.kt b/datetime/src/commonMain/kotlin/com/revenuecat/purchases/kmp/datetime/Transaction.kt
deleted file mode 100644
index 4850c1e9f..000000000
--- a/datetime/src/commonMain/kotlin/com/revenuecat/purchases/kmp/datetime/Transaction.kt
+++ /dev/null
@@ -1,16 +0,0 @@
-package com.revenuecat.purchases.kmp.datetime
-
-import com.revenuecat.purchases.kmp.models.Transaction
-import kotlinx.datetime.Instant
-
-/**
- * The purchase instant.
- */
-@Deprecated(
- message = "Instant properties have been moved to member fields. The entire " +
- "purchases-kmp-datetime module is deprecated and can be removed from your Gradle " +
- "files.",
- replaceWith = ReplaceWith("purchaseDate"),
-)
-public val Transaction.purchaseInstant: Instant
- get() = Instant.fromEpochMilliseconds(purchaseDateMillis)
diff --git a/fastlane/Fastfile b/fastlane/Fastfile
index 097b8685b..959200857 100644
--- a/fastlane/Fastfile
+++ b/fastlane/Fastfile
@@ -24,7 +24,6 @@ FILES_WITH_SDK_VERSION = {
desc "Bumps version, edits changelog, and creates pull request"
lane :bump do |options|
- phc_version = get_phc_version
bump_version_update_changelog_create_pr(
current_version: current_version_number,
changelog_latest_path: PATH_CHANGELOG_LATEST,
@@ -36,16 +35,10 @@ lane :bump do |options|
editor: options[:editor],
next_version: options[:next_version],
automatic_release: options[:automatic_release],
- hybrid_common_version: phc_version,
versions_file_path: PATH_VERSIONS,
- is_prerelease: options[:is_prerelease],
- append_phc_version_if_next_version_is_not_prerelease: true
- )
- update_hybrids_versions_file(
- versions_file_path: PATH_VERSIONS,
- new_sdk_version: current_version_number,
- hybrid_common_version: phc_version
+ is_prerelease: options[:is_prerelease]
)
+ update_versions_file(current_version_number)
commit_current_changes(commit_message: 'Updates VERSIONS.md')
push_to_git_remote(set_upstream: true)
end
@@ -68,62 +61,6 @@ lane :automatic_bump do |options|
bump(options)
end
-desc "Update purchases-hybrid-common dependency"
-lane :update_hybrid_common do |options|
- if options[:dry_run]
- dry_run = true
- end
- if options[:version]
- new_phc_version = options[:version]
- else
- UI.user_error!("Missing `version` argument")
- end
-
- current_phc_version = get_phc_version
-
- UI.message("ℹ️ Current Purchases Hybrid Common version: #{current_phc_version}")
- UI.message("ℹ️ Setting Purchases Hybrid Common version: #{new_phc_version}")
- files_to_update = {
- "gradle/libs.versions.toml" => ["revenuecat-common = \"{x}\""],
- "core/core.podspec" => ["spec.dependency 'PurchasesHybridCommon', '{x}'"],
- "mappings/mappings.podspec" => ["spec.dependency 'PurchasesHybridCommon', '{x}'"],
- "models/models.podspec" => ["spec.dependency 'PurchasesHybridCommon', '{x}'"],
- "revenuecatui/revenuecatui.podspec" => ["spec.dependency 'PurchasesHybridCommonUI', '{x}'"],
- "iosApp/Podfile" => [" pod 'PurchasesHybridCommon', '{x}'", " pod 'PurchasesHybridCommonUI', '{x}'"],
- }
-
- if dry_run
- UI.message("ℹ️ Nothing more to do, dry_run: true")
- next
- end
-
- bump_phc_version(
- repo_name: REPO_NAME,
- files_to_update: files_to_update,
- current_version: current_phc_version,
- next_version: new_phc_version,
- open_pr: options[:open_pr] || false,
- automatic_release: options[:automatic_release] || false
- )
- # Making sure Podfile.lock is updated.
- podfile = "iosApp/Podfile"
- cocoapods(
- podfile: podfile,
- repo_update: true
- )
- if options[:open_pr]
- git_commit(
- path: ["#{podfile}.lock"],
- message: "Updates Podfile.lock",
- skip_git_hooks: true
- )
- push_to_git_remote(
- tags: false,
- no_verify: true
- )
- end
-end
-
desc "Opens a PR updating the SDK version to the next minor, appending -SNAPSHOT."
lane :prepare_next_snapshot_version do |options|
create_next_snapshot_version(
@@ -179,40 +116,67 @@ lane :github_release do |options|
)
end
-def get_phc_version
- android_phc_version = File.read("../gradle/libs.versions.toml")
- .match("revenuecat-common = \"(.*)\"")
- .captures[0]
- UI.user_error!("Android PHC version not found.") if android_phc_version.nil?
-
- ios_phc_version = File.read("../core/core.podspec")
- .match("spec.dependency 'PurchasesHybridCommon', '(.*)'")
+def current_version_number
+ File.read("../gradle/libs.versions.toml")
+ .match('revenuecat-kmp = "(.*)"')
.captures[0]
- UI.user_error!("iOS PHC version not found.") if ios_phc_version.nil?
+end
- ios_phc_ui_version = File.read("../revenuecatui/revenuecatui.podspec")
- .match("spec.dependency 'PurchasesHybridCommonUI', '(.*)'")
+def get_android_version
+ version = File.read("../gradle/libs.versions.toml")
+ .match('revenuecat-android = "(.*)"')
.captures[0]
- UI.user_error!("iOS PHC UI version not found.") if ios_phc_ui_version.nil?
-
- versions_mismatch_error = "Found multiple PHC (UI) versions. Please make sure to use only 1."
- versions_match = android_phc_version == ios_phc_version && ios_phc_version == ios_phc_ui_version
- UI.user_error!(versions_mismatch_error) unless versions_match
-
- android_phc_version
- end
+ UI.user_error!("Android version not found in libs.versions.toml") if version.nil?
+ version
+end
- def current_version_number
- File.read("../gradle/libs.versions.toml")
- .match("revenuecat-kmp = \"(.*)\"")
- .captures[0]
- end
+def get_ios_version
+ version = File.read("../upstream/purchases-ios/.version").strip
+ UI.user_error!("iOS version not found in upstream/purchases-ios/.version") if version.nil? || version.empty?
+ version
+end
- def check_no_git_tag_exists(version_number)
- if git_tag_exists(tag: version_number, remote: true, remote_name: 'origin')
- raise "git tag with version #{version_number} already exists!"
- end
+def get_billing_client_version(android_version)
+ require 'base64'
+ response = github_api(
+ server_url: 'https://api.github.com',
+ api_token: ENV["GITHUB_TOKEN"],
+ http_method: 'GET',
+ path: "/repos/RevenueCat/purchases-android/contents/gradle/libs.versions.toml?ref=#{android_version}"
+ )
+ contents = Base64.decode64(response[:json]['content'])
+ matches = contents.match('bc8 = "(.*)"')
+ UI.user_error!("Could not find Play Billing Library version for purchases-android #{android_version}") unless matches
+ matches.captures[0]
+end
+
+def update_versions_file(sdk_version)
+ android_version = get_android_version
+ ios_version = get_ios_version
+ billing_version = get_billing_client_version(android_version)
+
+ UI.message("Android version: #{android_version}")
+ UI.message("iOS version: #{ios_version}")
+ UI.message("Play Billing Library version: #{billing_version}")
+
+ versions_path = File.join('..', PATH_VERSIONS)
+ lines = File.readlines(versions_path)
+ new_line = [
+ sdk_version,
+ "[#{ios_version}](https://github.com/RevenueCat/purchases-ios/releases/tag/#{ios_version})",
+ "[#{android_version}](https://github.com/RevenueCat/purchases-android/releases/tag/#{android_version})",
+ "N/A",
+ "[#{billing_version}](https://developer.android.com/google/play/billing/release-notes)"
+ ].join(' | ')
+ lines.insert(2, "| #{new_line} |\n")
+ File.write(versions_path, lines.join)
+end
+
+def check_no_git_tag_exists(version_number)
+ if git_tag_exists(tag: version_number, remote: true, remote_name: 'origin')
+ raise "git tag with version #{version_number} already exists!"
end
+end
# Publishes the SDK. If the current version is a SNAPSHOT version, it will be published
# to Sonatype's snapshots repository, otherwise it will be published to Maven Central.
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index f26c73b48..1892aa0f2 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -3,16 +3,16 @@ agp = "8.13.2"
android-compileSdk = "35"
android-minSdk = "21"
android-targetSdk = "35"
-compose = "1.8.0"
+compose = "1.9.3"
detekt = "1.23.6"
dokkatoo = "2.3.1"
ios-deploymentTarget-core = "13.0"
ios-deploymentTarget-ui = "15.0"
java = "1.8"
junit = "5.10.0"
-kotlin = "2.1.21"
+kotlin = "2.3.20"
mavenPublish = "0.33.0"
-revenuecat-common = "17.55.1"
+revenuecat-android = "9.26.1"
revenuecat-kmp = "2.11.0-SNAPSHOT"
[libraries]
@@ -28,14 +28,14 @@ arrow-core = { module = "io.arrow-kt:arrow-core", version = "1.2.4" }
junit-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junit" }
junit-core = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit" }
junit-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junit" }
+junit-platformLauncher = { module = "org.junit.platform:junit-platform-launcher" }
kotlin-gradlePlugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
kotlin-test-assertions = { module = "org.jetbrains.kotlin:kotlin-test-common", version.ref = "kotlin" }
kotlin-test-annotations = { module = "org.jetbrains.kotlin:kotlin-test-annotations-common", version.ref = "kotlin" }
kotlin-test-core = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" }
-kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version = "0.6.0" }
-revenuecat-common = { module = "com.revenuecat.purchases:purchases-hybrid-common", version.ref = "revenuecat-common" }
-revenuecat-commonUi = { module = "com.revenuecat.purchases:purchases-hybrid-common-ui", version.ref = "revenuecat-common" }
+revenuecat-android = { module = "com.revenuecat.purchases:purchases", version.ref = "revenuecat-android" }
+revenuecatUi-android = { module = "com.revenuecat.purchases:purchases-ui", version.ref = "revenuecat-android" }
[plugins]
adamko-dokkatoo-html = { id = "dev.adamko.dokkatoo-html", version.ref = "dokkatoo" }
@@ -46,7 +46,6 @@ codingfeline-buildkonfig = { id = "com.codingfeline.buildkonfig", version = "0.1
iurysouza-modulegraph = { id = "dev.iurysouza.modulegraph", version = "0.10.0" }
jetbrains-compose = { id = "org.jetbrains.compose", version.ref = "compose" }
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
-kotlin-cocoapods = { id = "org.jetbrains.kotlin.native.cocoapods", version.ref = "kotlin" }
kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
kotlinx-binaryCompatibilityValidator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version = "0.17.0" }
vanniktech-mavenPublish = { id = "com.vanniktech.maven.publish", version.ref = "mavenPublish" }
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index aaaabb3cb..c61a118f7 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.4-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
diff --git a/iosApp/Podfile b/iosApp/Podfile
deleted file mode 100644
index f73aa0f08..000000000
--- a/iosApp/Podfile
+++ /dev/null
@@ -1,11 +0,0 @@
-# Uncomment the next line to define a global platform for your project
-# platform :ios, '9.0'
-
-target 'iosApp' do
- # Comment the next line if you don't want to use dynamic frameworks
- # use_frameworks!
-
- # Pods for iosApp
- pod 'PurchasesHybridCommon', '17.55.1'
- pod 'PurchasesHybridCommonUI', '17.55.1'
-end
diff --git a/iosApp/Podfile.lock b/iosApp/Podfile.lock
deleted file mode 100644
index 4fc5d655d..000000000
--- a/iosApp/Podfile.lock
+++ /dev/null
@@ -1,30 +0,0 @@
-PODS:
- - PurchasesHybridCommon (17.51.1):
- - RevenueCat (= 5.65.0)
- - PurchasesHybridCommonUI (17.51.1):
- - PurchasesHybridCommon (= 17.51.1)
- - RevenueCatUI (= 5.65.0)
- - RevenueCat (5.65.0)
- - RevenueCatUI (5.65.0):
- - RevenueCat (= 5.65.0)
-
-DEPENDENCIES:
- - PurchasesHybridCommon (= 17.51.1)
- - PurchasesHybridCommonUI (= 17.51.1)
-
-SPEC REPOS:
- trunk:
- - PurchasesHybridCommon
- - PurchasesHybridCommonUI
- - RevenueCat
- - RevenueCatUI
-
-SPEC CHECKSUMS:
- PurchasesHybridCommon: 204de8c7d0b182240ac708cf6a05881bd99ceca9
- PurchasesHybridCommonUI: a4cb430ad365d27258653ce1bafd4dd5b130d7e4
- RevenueCat: 9a37739bdf42af84dd87e023db340de7e0f9d874
- RevenueCatUI: 1a503c1713ab8f598a0eb55e9954de7e3616aa5b
-
-PODFILE CHECKSUM: 0d2197b4fbe127ac69e58d0586b1916aa660770a
-
-COCOAPODS: 1.16.2
diff --git a/iosApp/iosApp.xcodeproj/project.pbxproj b/iosApp/iosApp.xcodeproj/project.pbxproj
index 1384f7e39..72c82946b 100644
--- a/iosApp/iosApp.xcodeproj/project.pbxproj
+++ b/iosApp/iosApp.xcodeproj/project.pbxproj
@@ -9,7 +9,6 @@
/* Begin PBXBuildFile section */
058557BB273AAA24004C7B11 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557BA273AAA24004C7B11 /* Assets.xcassets */; };
058557D9273AAEEB004C7B11 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */; };
- 0E12A6F5E3C5A368A45100AA /* libPods-iosApp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7970694DAED6D108F4AB7D20 /* libPods-iosApp.a */; };
2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2152FB032600AC8F00CF470E /* iOSApp.swift */; };
7555FF83242A565900829871 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7555FF82242A565900829871 /* ContentView.swift */; };
FD277EB72D2C6C5B00E392B7 /* SKConfig.storekit in Resources */ = {isa = PBXBuildFile; fileRef = FD277EB62D2C6C5B00E392B7 /* SKConfig.storekit */; };
@@ -22,10 +21,7 @@
7555FF7B242A565900829871 /* purchases-kmp Sample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "purchases-kmp Sample.app"; sourceTree = BUILT_PRODUCTS_DIR; };
7555FF82242A565900829871 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; };
7555FF8C242A565B00829871 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
- 7970694DAED6D108F4AB7D20 /* libPods-iosApp.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-iosApp.a"; sourceTree = BUILT_PRODUCTS_DIR; };
AB3632DC29227652001CCB65 /* Config.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = ""; };
- ADB7642423335C54609E9121 /* Pods-iosApp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iosApp.debug.xcconfig"; path = "Target Support Files/Pods-iosApp/Pods-iosApp.debug.xcconfig"; sourceTree = ""; };
- E4E97109987A6338537554BE /* Pods-iosApp.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iosApp.release.xcconfig"; path = "Target Support Files/Pods-iosApp/Pods-iosApp.release.xcconfig"; sourceTree = ""; };
FD277EB62D2C6C5B00E392B7 /* SKConfig.storekit */ = {isa = PBXFileReference; lastKnownFileType = text; path = SKConfig.storekit; sourceTree = ""; };
/* End PBXFileReference section */
@@ -34,7 +30,6 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- 0E12A6F5E3C5A368A45100AA /* libPods-iosApp.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -49,31 +44,12 @@
path = "Preview Content";
sourceTree = "";
};
- 38C5C97CCB7C1B4002B5B2EE /* Pods */ = {
- isa = PBXGroup;
- children = (
- ADB7642423335C54609E9121 /* Pods-iosApp.debug.xcconfig */,
- E4E97109987A6338537554BE /* Pods-iosApp.release.xcconfig */,
- );
- path = Pods;
- sourceTree = "";
- };
- 42799AB246E5F90AF97AA0EF /* Frameworks */ = {
- isa = PBXGroup;
- children = (
- 7970694DAED6D108F4AB7D20 /* libPods-iosApp.a */,
- );
- name = Frameworks;
- sourceTree = "";
- };
7555FF72242A565900829871 = {
isa = PBXGroup;
children = (
AB1DB47929225F7C00F7AF9C /* Configuration */,
7555FF7D242A565900829871 /* iosApp */,
7555FF7C242A565900829871 /* Products */,
- 42799AB246E5F90AF97AA0EF /* Frameworks */,
- 38C5C97CCB7C1B4002B5B2EE /* Pods */,
);
sourceTree = "";
};
@@ -113,12 +89,10 @@
isa = PBXNativeTarget;
buildConfigurationList = 7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget "iosApp" */;
buildPhases = (
- E546C444910C3630A98ADF58 /* [CP] Check Pods Manifest.lock */,
F36B1CEB2AD83DDC00CB74D5 /* Compile Kotlin Framework */,
7555FF77242A565900829871 /* Sources */,
B92378962B6B1156000C7307 /* Frameworks */,
7555FF79242A565900829871 /* Resources */,
- BADF33751A0CFC72A3F9017A /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@@ -176,45 +150,6 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
- BADF33751A0CFC72A3F9017A /* [CP] Copy Pods Resources */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputFileListPaths = (
- "${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-resources-${CONFIGURATION}-input-files.xcfilelist",
- );
- name = "[CP] Copy Pods Resources";
- outputFileListPaths = (
- "${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-resources-${CONFIGURATION}-output-files.xcfilelist",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-resources.sh\"\n";
- showEnvVarsInLog = 0;
- };
- E546C444910C3630A98ADF58 /* [CP] Check Pods Manifest.lock */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputFileListPaths = (
- );
- inputPaths = (
- "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
- "${PODS_ROOT}/Manifest.lock",
- );
- name = "[CP] Check Pods Manifest.lock";
- outputFileListPaths = (
- );
- outputPaths = (
- "$(DERIVED_FILE_DIR)/Pods-iosApp-checkManifestLockResult.txt",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
- showEnvVarsInLog = 0;
- };
F36B1CEB2AD83DDC00CB74D5 /* Compile Kotlin Framework */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
@@ -368,7 +303,6 @@
};
7555FFA6242A565B00829871 /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = ADB7642423335C54609E9121 /* Pods-iosApp.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_IDENTITY = "Apple Development";
@@ -401,7 +335,6 @@
};
7555FFA7242A565B00829871 /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = E4E97109987A6338537554BE /* Pods-iosApp.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_IDENTITY = "Apple Development";
diff --git a/iosApp/iosApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/iosApp/iosApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 000000000..919434a62
--- /dev/null
+++ b/iosApp/iosApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/iosApp/iosApp.xcodeproj/xcshareddata/xcschemes/iosApp.xcscheme b/iosApp/iosApp.xcodeproj/xcshareddata/xcschemes/iosApp.xcscheme
index 226434528..645b6baf2 100644
--- a/iosApp/iosApp.xcodeproj/xcshareddata/xcschemes/iosApp.xcscheme
+++ b/iosApp/iosApp.xcodeproj/xcshareddata/xcschemes/iosApp.xcscheme
@@ -1,11 +1,9 @@
+ buildImplicitDependencies = "YES">
-
-
+
+
-
-
-
-
diff --git a/iosApp/iosApp.xcworkspace/contents.xcworkspacedata b/iosApp/iosApp.xcworkspace/contents.xcworkspacedata
deleted file mode 100644
index 4b6f2d363..000000000
--- a/iosApp/iosApp.xcworkspace/contents.xcworkspacedata
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/iosApp/iosApp.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/iosApp/iosApp.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
deleted file mode 100644
index 18d981003..000000000
--- a/iosApp/iosApp.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
- IDEDidComputeMac32BitWarning
-
-
-
diff --git a/kn-core/.gitignore b/kn-core/.gitignore
new file mode 100644
index 000000000..d74241ef4
--- /dev/null
+++ b/kn-core/.gitignore
@@ -0,0 +1,2 @@
+/build
+.build
diff --git a/kn-core/api/kn-core.api b/kn-core/api/kn-core.api
new file mode 100644
index 000000000..e69de29bb
diff --git a/kn-core/api/kn-core.klib.api b/kn-core/api/kn-core.klib.api
new file mode 100644
index 000000000..1ec12c359
--- /dev/null
+++ b/kn-core/api/kn-core.klib.api
@@ -0,0 +1,8 @@
+// Klib ABI Dump
+// Targets: [iosArm64, iosSimulatorArm64, iosX64]
+// Rendering settings:
+// - Signature version: 2
+// - Show manifest properties: true
+// - Show declarations: true
+
+// Library unique name:
diff --git a/kn-core/build.gradle.kts b/kn-core/build.gradle.kts
new file mode 100644
index 000000000..c27f4c06a
--- /dev/null
+++ b/kn-core/build.gradle.kts
@@ -0,0 +1,33 @@
+import com.revenuecat.purchases.kmp.buildlogic.swift.swiftPackage
+
+plugins {
+ id("revenuecat-library")
+}
+
+kotlin {
+ sourceSets {
+ iosMain.dependencies {
+ swiftPackage(
+ path = rootProject.file("upstream/purchases-ios"),
+ target = "RevenueCat",
+ packageName = "swiftPMImport.com.revenuecat.purchases.kn.core",
+ customDeclarations = """
+ // Force cinterop binding generation for types otherwise not in the public API
+ static inline int __forceBindings(
+ enum RCStoreMessageType _1
+ ) { return 0; }
+ """.trimIndent()
+ )
+
+ swiftPackage(
+ path = file("src/swift"),
+ target = "AdditionalSwift",
+ packageName = "swiftPMImport.com.revenuecat.purchases.kn.core.additional"
+ )
+ }
+ }
+}
+
+android {
+ namespace = "com.revenuecat.purchases.kn.core"
+}
diff --git a/kn-core/src/iosMain/kotlin/com/revenuecat/purchases/kn/core/Dummy.kt b/kn-core/src/iosMain/kotlin/com/revenuecat/purchases/kn/core/Dummy.kt
new file mode 100644
index 000000000..40990e7de
--- /dev/null
+++ b/kn-core/src/iosMain/kotlin/com/revenuecat/purchases/kn/core/Dummy.kt
@@ -0,0 +1,8 @@
+@file:Suppress("unused")
+
+package com.revenuecat.purchases.kn.core
+
+/**
+ * We need at least 1 Kotlin source file for the kn-core module to be published.
+ */
+internal object Dummy
diff --git a/kn-core/src/swift/Package.swift b/kn-core/src/swift/Package.swift
new file mode 100644
index 000000000..63571d3de
--- /dev/null
+++ b/kn-core/src/swift/Package.swift
@@ -0,0 +1,20 @@
+// swift-tools-version:5.9
+import PackageDescription
+
+let package = Package(
+ name: "AdditionalSwift",
+ platforms: [
+ .macOS(.v10_15),
+ .watchOS("6.2"),
+ .tvOS(.v13),
+ .iOS(.v13),
+ .visionOS(.v1)
+ ],
+ products: [
+ .library(name: "AdditionalSwift", targets: ["AdditionalSwift"])
+ ],
+ targets: [
+ .target(name: "AdditionalSwift")
+ ]
+)
+
diff --git a/kn-core/src/swift/Sources/AdditionalSwift/AppleApiAvailability.swift b/kn-core/src/swift/Sources/AdditionalSwift/AppleApiAvailability.swift
new file mode 100644
index 000000000..8997c4651
--- /dev/null
+++ b/kn-core/src/swift/Sources/AdditionalSwift/AppleApiAvailability.swift
@@ -0,0 +1,75 @@
+import Foundation
+
+/// Provides runtime availability checks for Apple platform APIs.
+@objc
+public class AppleApiAvailability: NSObject {
+ /// Determines if the Win-Back Offer APIs are available on the current device.
+ ///
+ /// Note: This only checks if the APIs are available in the current OS version,
+ /// not if the SDK is using StoreKit 2, which is required for the APIs.
+ ///
+ /// - Returns: `true` if the Win-Back Offer APIs are available, `false` otherwise.
+ @objc
+ public func isWinBackOfferAPIAvailable() -> Bool {
+ if #available(iOS 18.0, macOS 15.0, tvOS 18.0, watchOS 11.0, visionOS 2.0, *) {
+ return true
+ } else {
+ return false
+ }
+ }
+
+ /// Determines if the enableAdServicesAttributionTokenCollection API is available on the current device.
+ ///
+ /// - Returns: `true` if the ``CommonFunctionality/enableAdServicesAttributionTokenCollection()`` API is available,
+ /// `false` otherwise.
+ @objc
+ public func isEnableAdServicesAttributionTokenCollectionAPIAvailable() -> Bool {
+ #if os(tvOS) || os(watchOS)
+ return false
+ #else
+ if #available(iOS 14.3, macOS 11.1, macCatalyst 14.3, *) {
+ return true
+ } else {
+ return false
+ }
+ #endif
+ }
+
+ /// Determines if the `presentCodeRedemptionSheet` API is available on the current device.
+ ///
+ /// - Returns: `true` if `presentCodeRedemptionSheet` is available, `false` otherwise.
+ @objc
+ public func isCodeRedemptionSheetAPIAvailable() -> Bool {
+ #if os(tvOS) || os(watchOS) || os(macOS) || targetEnvironment(macCatalyst)
+ return false
+ #else
+ if #available(iOS 14.0, visionOS 1.0, *) {
+ return true
+ } else {
+ return false
+ }
+ #endif
+ }
+
+ @objc
+ public func isAdTrackingAPIAvailable() -> Bool {
+ if #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) {
+ return true
+ } else {
+ return false
+ }
+ }
+
+ /// Determines if the Custom Paywall Tracking APIs are available on the current device.
+ ///
+ /// - Returns: `true` if the Custom Paywall Tracking APIs (trackCustomPaywallImpression) are available,
+ /// `false` otherwise.
+ @objc
+ public func isCustomPaywallTrackingAPIAvailable() -> Bool {
+ if #available(iOS 15.0, tvOS 15.0, macOS 12.0, watchOS 8.0, *) {
+ return true
+ } else {
+ return false
+ }
+ }
+}
diff --git a/kn-ui/.gitignore b/kn-ui/.gitignore
new file mode 100644
index 000000000..d74241ef4
--- /dev/null
+++ b/kn-ui/.gitignore
@@ -0,0 +1,2 @@
+/build
+.build
diff --git a/kn-ui/api/kn-ui.api b/kn-ui/api/kn-ui.api
new file mode 100644
index 000000000..e69de29bb
diff --git a/kn-ui/api/kn-ui.klib.api b/kn-ui/api/kn-ui.klib.api
new file mode 100644
index 000000000..fc9165d7a
--- /dev/null
+++ b/kn-ui/api/kn-ui.klib.api
@@ -0,0 +1,19 @@
+// Klib ABI Dump
+// Targets: [iosArm64, iosSimulatorArm64, iosX64]
+// Rendering settings:
+// - Signature version: 2
+// - Show manifest properties: true
+// - Show declarations: true
+
+// Library unique name:
+final val swiftPMImport.com.revenuecat.purchases.kn.ui.resources/swiftPMImport_com_revenuecat_purchases_kn_ui_resources_Res_array$stableprop // swiftPMImport.com.revenuecat.purchases.kn.ui.resources/swiftPMImport_com_revenuecat_purchases_kn_ui_resources_Res_array$stableprop|#static{}swiftPMImport_com_revenuecat_purchases_kn_ui_resources_Res_array$stableprop[0]
+final val swiftPMImport.com.revenuecat.purchases.kn.ui.resources/swiftPMImport_com_revenuecat_purchases_kn_ui_resources_Res_drawable$stableprop // swiftPMImport.com.revenuecat.purchases.kn.ui.resources/swiftPMImport_com_revenuecat_purchases_kn_ui_resources_Res_drawable$stableprop|#static{}swiftPMImport_com_revenuecat_purchases_kn_ui_resources_Res_drawable$stableprop[0]
+final val swiftPMImport.com.revenuecat.purchases.kn.ui.resources/swiftPMImport_com_revenuecat_purchases_kn_ui_resources_Res_font$stableprop // swiftPMImport.com.revenuecat.purchases.kn.ui.resources/swiftPMImport_com_revenuecat_purchases_kn_ui_resources_Res_font$stableprop|#static{}swiftPMImport_com_revenuecat_purchases_kn_ui_resources_Res_font$stableprop[0]
+final val swiftPMImport.com.revenuecat.purchases.kn.ui.resources/swiftPMImport_com_revenuecat_purchases_kn_ui_resources_Res_plurals$stableprop // swiftPMImport.com.revenuecat.purchases.kn.ui.resources/swiftPMImport_com_revenuecat_purchases_kn_ui_resources_Res_plurals$stableprop|#static{}swiftPMImport_com_revenuecat_purchases_kn_ui_resources_Res_plurals$stableprop[0]
+final val swiftPMImport.com.revenuecat.purchases.kn.ui.resources/swiftPMImport_com_revenuecat_purchases_kn_ui_resources_Res_string$stableprop // swiftPMImport.com.revenuecat.purchases.kn.ui.resources/swiftPMImport_com_revenuecat_purchases_kn_ui_resources_Res_string$stableprop|#static{}swiftPMImport_com_revenuecat_purchases_kn_ui_resources_Res_string$stableprop[0]
+
+final fun swiftPMImport.com.revenuecat.purchases.kn.ui.resources/swiftPMImport_com_revenuecat_purchases_kn_ui_resources_Res_array$stableprop_getter(): kotlin/Int // swiftPMImport.com.revenuecat.purchases.kn.ui.resources/swiftPMImport_com_revenuecat_purchases_kn_ui_resources_Res_array$stableprop_getter|swiftPMImport_com_revenuecat_purchases_kn_ui_resources_Res_array$stableprop_getter(){}[0]
+final fun swiftPMImport.com.revenuecat.purchases.kn.ui.resources/swiftPMImport_com_revenuecat_purchases_kn_ui_resources_Res_drawable$stableprop_getter(): kotlin/Int // swiftPMImport.com.revenuecat.purchases.kn.ui.resources/swiftPMImport_com_revenuecat_purchases_kn_ui_resources_Res_drawable$stableprop_getter|swiftPMImport_com_revenuecat_purchases_kn_ui_resources_Res_drawable$stableprop_getter(){}[0]
+final fun swiftPMImport.com.revenuecat.purchases.kn.ui.resources/swiftPMImport_com_revenuecat_purchases_kn_ui_resources_Res_font$stableprop_getter(): kotlin/Int // swiftPMImport.com.revenuecat.purchases.kn.ui.resources/swiftPMImport_com_revenuecat_purchases_kn_ui_resources_Res_font$stableprop_getter|swiftPMImport_com_revenuecat_purchases_kn_ui_resources_Res_font$stableprop_getter(){}[0]
+final fun swiftPMImport.com.revenuecat.purchases.kn.ui.resources/swiftPMImport_com_revenuecat_purchases_kn_ui_resources_Res_plurals$stableprop_getter(): kotlin/Int // swiftPMImport.com.revenuecat.purchases.kn.ui.resources/swiftPMImport_com_revenuecat_purchases_kn_ui_resources_Res_plurals$stableprop_getter|swiftPMImport_com_revenuecat_purchases_kn_ui_resources_Res_plurals$stableprop_getter(){}[0]
+final fun swiftPMImport.com.revenuecat.purchases.kn.ui.resources/swiftPMImport_com_revenuecat_purchases_kn_ui_resources_Res_string$stableprop_getter(): kotlin/Int // swiftPMImport.com.revenuecat.purchases.kn.ui.resources/swiftPMImport_com_revenuecat_purchases_kn_ui_resources_Res_string$stableprop_getter|swiftPMImport_com_revenuecat_purchases_kn_ui_resources_Res_string$stableprop_getter(){}[0]
diff --git a/kn-ui/build.gradle.kts b/kn-ui/build.gradle.kts
new file mode 100644
index 000000000..516f0861e
--- /dev/null
+++ b/kn-ui/build.gradle.kts
@@ -0,0 +1,32 @@
+import com.revenuecat.purchases.kmp.buildlogic.swift.model.SwiftSettings
+import com.revenuecat.purchases.kmp.buildlogic.swift.swiftPackage
+
+plugins {
+ id("revenuecat-library")
+ alias(libs.plugins.jetbrains.compose)
+ alias(libs.plugins.compose.compiler)
+}
+
+kotlin {
+ sourceSets {
+ commonMain.dependencies {
+ implementation(compose.components.resources)
+ implementation(compose.runtime)
+ }
+
+ iosMain.dependencies {
+ swiftPackage(
+ path = rootProject.file("upstream/purchases-ios"),
+ target = "RevenueCatUI",
+ packageName = "swiftPMImport.com.revenuecat.purchases.kn.ui",
+ swiftSettings = SwiftSettings {
+ define("COMPOSE_RESOURCES")
+ }
+ )
+ }
+ }
+}
+
+android {
+ namespace = "com.revenuecat.purchases.kn.ui"
+}
diff --git a/kn-ui/src/iosMain/kotlin/com/revenuecat/purchases/kn/ui/Dummy.kt b/kn-ui/src/iosMain/kotlin/com/revenuecat/purchases/kn/ui/Dummy.kt
new file mode 100644
index 000000000..7040eeee0
--- /dev/null
+++ b/kn-ui/src/iosMain/kotlin/com/revenuecat/purchases/kn/ui/Dummy.kt
@@ -0,0 +1,8 @@
+@file:Suppress("unused")
+
+package com.revenuecat.purchases.kn.ui
+
+/**
+ * We need at least 1 Kotlin source file for the kn-ui module to be published.
+ */
+internal object Dummy
diff --git a/mappings/build.gradle.kts b/mappings/build.gradle.kts
index 979db9d1a..49e78612d 100644
--- a/mappings/build.gradle.kts
+++ b/mappings/build.gradle.kts
@@ -1,6 +1,5 @@
plugins {
id("revenuecat-library")
- alias(libs.plugins.kotlin.cocoapods)
}
kotlin {
@@ -9,27 +8,15 @@ kotlin {
api(projects.models)
}
androidMain.dependencies {
- api(libs.revenuecat.common)
+ api(libs.revenuecat.android)
}
- commonTest.dependencies {
- implementation(libs.kotlin.test.annotations)
- implementation(libs.kotlin.test.assertions)
+ iosMain.dependencies {
+ implementation(projects.knCore)
}
androidUnitTest.dependencies {
implementation(libs.kotlin.test.junit)
}
}
-
- cocoapods {
- version = libs.versions.revenuecat.kmp.get()
- ios.deploymentTarget = libs.versions.ios.deploymentTarget.core.get()
-
- pod("PurchasesHybridCommon") {
- version = libs.versions.revenuecat.common.get()
- extraOpts += listOf("-compiler-option", "-fmodules")
- packageName = "swiftPMImport.com.revenuecat.purchases.kn.core"
- }
- }
}
android {
diff --git a/mappings/mappings.podspec b/mappings/mappings.podspec
deleted file mode 100644
index 24fca30e6..000000000
--- a/mappings/mappings.podspec
+++ /dev/null
@@ -1,54 +0,0 @@
-Pod::Spec.new do |spec|
- spec.name = 'mappings'
- spec.version = '2.11.0-SNAPSHOT'
- spec.homepage = ''
- spec.source = { :http=> ''}
- spec.authors = ''
- spec.license = ''
- spec.summary = ''
- spec.vendored_frameworks = 'build/cocoapods/framework/mappings.framework'
- spec.libraries = 'c++'
- spec.ios.deployment_target = '13.0'
- spec.dependency 'PurchasesHybridCommon', '17.55.1'
-
- if !Dir.exist?('build/cocoapods/framework/mappings.framework') || Dir.empty?('build/cocoapods/framework/mappings.framework')
- raise "
-
- Kotlin framework 'mappings' doesn't exist yet, so a proper Xcode project can't be generated.
- 'pod install' should be executed after running ':generateDummyFramework' Gradle task:
-
- ./gradlew :mappings:generateDummyFramework
-
- Alternatively, proper pod installation is performed during Gradle sync in the IDE (if Podfile location is set)"
- end
-
- spec.xcconfig = {
- 'ENABLE_USER_SCRIPT_SANDBOXING' => 'NO',
- }
-
- spec.pod_target_xcconfig = {
- 'KOTLIN_PROJECT_PATH' => ':mappings',
- 'PRODUCT_MODULE_NAME' => 'mappings',
- }
-
- spec.script_phases = [
- {
- :name => 'Build mappings',
- :execution_position => :before_compile,
- :shell_path => '/bin/sh',
- :script => <<-SCRIPT
- if [ "YES" = "$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED" ]; then
- echo "Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \"YES\""
- exit 0
- fi
- set -ev
- REPO_ROOT="$PODS_TARGET_SRCROOT"
- "$REPO_ROOT/../gradlew" -p "$REPO_ROOT" $KOTLIN_PROJECT_PATH:syncFramework \
- -Pkotlin.native.cocoapods.platform=$PLATFORM_NAME \
- -Pkotlin.native.cocoapods.archs="$ARCHS" \
- -Pkotlin.native.cocoapods.configuration="$CONFIGURATION"
- SCRIPT
- }
- ]
-
-end
\ No newline at end of file
diff --git a/mappings/src/androidMain/kotlin/com/revenuecat/purchases/kmp/mappings/EntitlementVerificationMode.android.kt b/mappings/src/androidMain/kotlin/com/revenuecat/purchases/kmp/mappings/EntitlementVerificationMode.android.kt
new file mode 100644
index 000000000..2bba0b70b
--- /dev/null
+++ b/mappings/src/androidMain/kotlin/com/revenuecat/purchases/kmp/mappings/EntitlementVerificationMode.android.kt
@@ -0,0 +1,10 @@
+package com.revenuecat.purchases.kmp.mappings
+
+import com.revenuecat.purchases.kmp.models.EntitlementVerificationMode
+import com.revenuecat.purchases.EntitlementVerificationMode as AndroidEntitlementVerificationMode
+
+public fun EntitlementVerificationMode.toAndroidEntitlementVerificationMode(): AndroidEntitlementVerificationMode =
+ when (this) {
+ EntitlementVerificationMode.DISABLED -> AndroidEntitlementVerificationMode.DISABLED
+ EntitlementVerificationMode.INFORMATIONAL -> AndroidEntitlementVerificationMode.INFORMATIONAL
+ }
diff --git a/mappings/src/androidMain/kotlin/com/revenuecat/purchases/kmp/mappings/PurchasesAreCompletedBy.android.kt b/mappings/src/androidMain/kotlin/com/revenuecat/purchases/kmp/mappings/PurchasesAreCompletedBy.android.kt
new file mode 100644
index 000000000..3c22eb220
--- /dev/null
+++ b/mappings/src/androidMain/kotlin/com/revenuecat/purchases/kmp/mappings/PurchasesAreCompletedBy.android.kt
@@ -0,0 +1,10 @@
+package com.revenuecat.purchases.kmp.mappings
+
+import com.revenuecat.purchases.kmp.models.PurchasesAreCompletedBy
+import com.revenuecat.purchases.PurchasesAreCompletedBy as AndroidPurchasesAreCompletedBy
+
+public fun PurchasesAreCompletedBy.toAndroidPurchasesAreCompletedBy(): AndroidPurchasesAreCompletedBy =
+ when (this) {
+ is PurchasesAreCompletedBy.RevenueCat -> AndroidPurchasesAreCompletedBy.REVENUECAT
+ is PurchasesAreCompletedBy.MyApp -> AndroidPurchasesAreCompletedBy.MY_APP
+ }
diff --git a/mappings/src/commonMain/kotlin/com/revenuecat/purchases/kmp/mappings/PurchasesAreCompletedBy.kt b/mappings/src/commonMain/kotlin/com/revenuecat/purchases/kmp/mappings/PurchasesAreCompletedBy.kt
deleted file mode 100644
index ed2ae309f..000000000
--- a/mappings/src/commonMain/kotlin/com/revenuecat/purchases/kmp/mappings/PurchasesAreCompletedBy.kt
+++ /dev/null
@@ -1,18 +0,0 @@
-package com.revenuecat.purchases.kmp.mappings
-
-import com.revenuecat.purchases.kmp.models.PurchasesAreCompletedBy
-
-/**
- * Converts an instance of `PurchasesAreCompletedBy` to its corresponding string representation
- * suitable for usage with the PurchasesHybridCommon library.
- *
- * @return A `String` that represents the type of `PurchasesAreCompletedBy`:
- * - Returns `"REVENUECAT"` if the instance is of type `PurchasesAreCompletedBy.RevenueCat`.
- * - Returns `"MY_APP"` if the instance is of type `PurchasesAreCompletedBy.MyApp`.
- *
- */
-public fun PurchasesAreCompletedBy.toHybridString(): String =
- when(this) {
- is PurchasesAreCompletedBy.RevenueCat -> "REVENUECAT"
- is PurchasesAreCompletedBy.MyApp -> "MY_APP"
- }
diff --git a/mappings/src/commonTest/kotlin/com/revenuecat/purchases/kmp/mappings/PurchasesAreCompletedByTests.kt b/mappings/src/commonTest/kotlin/com/revenuecat/purchases/kmp/mappings/PurchasesAreCompletedByTests.kt
deleted file mode 100644
index bd2a2ca93..000000000
--- a/mappings/src/commonTest/kotlin/com/revenuecat/purchases/kmp/mappings/PurchasesAreCompletedByTests.kt
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.revenuecat.purchases.kmp.mappings
-
-import com.revenuecat.purchases.kmp.models.PurchasesAreCompletedBy
-import com.revenuecat.purchases.kmp.models.StoreKitVersion
-import kotlin.test.Test
-import kotlin.test.assertEquals
-
-class PurchasesAreCompletedByTests {
-
- @Test
- fun `toHybridString returns the correct values`() {
- val purchasesAreCompletedByMyApp = PurchasesAreCompletedBy.MyApp(StoreKitVersion.DEFAULT)
- val myAppHybridString = purchasesAreCompletedByMyApp.toHybridString()
-
- val purchasesAreCompletedByRevenueCat = PurchasesAreCompletedBy.RevenueCat
- val revenuecatHybridString = purchasesAreCompletedByRevenueCat.toHybridString()
-
- assertEquals("MY_APP", myAppHybridString)
- assertEquals("REVENUECAT", revenuecatHybridString)
- }
-}
diff --git a/mappings/src/iosMain/kotlin/com/revenuecat/purchases/kmp/mappings/AdEventTypes.ios.kt b/mappings/src/iosMain/kotlin/com/revenuecat/purchases/kmp/mappings/AdEventTypes.ios.kt
new file mode 100644
index 000000000..f957f7f28
--- /dev/null
+++ b/mappings/src/iosMain/kotlin/com/revenuecat/purchases/kmp/mappings/AdEventTypes.ios.kt
@@ -0,0 +1,94 @@
+package com.revenuecat.purchases.kmp.mappings
+
+import com.revenuecat.purchases.kmp.ExperimentalRevenueCatApi
+import com.revenuecat.purchases.kmp.models.AdDisplayedData
+import com.revenuecat.purchases.kmp.models.AdFailedToLoadData
+import com.revenuecat.purchases.kmp.models.AdFormat
+import com.revenuecat.purchases.kmp.models.AdLoadedData
+import com.revenuecat.purchases.kmp.models.AdMediatorName
+import com.revenuecat.purchases.kmp.models.AdOpenedData
+import com.revenuecat.purchases.kmp.models.AdRevenueData
+import com.revenuecat.purchases.kmp.models.AdRevenuePrecision
+import platform.Foundation.NSNumber
+import swiftPMImport.com.revenuecat.purchases.kn.core.RCAdDisplayed
+import swiftPMImport.com.revenuecat.purchases.kn.core.RCAdFailedToLoad
+import swiftPMImport.com.revenuecat.purchases.kn.core.RCAdFormat
+import swiftPMImport.com.revenuecat.purchases.kn.core.RCAdLoaded
+import swiftPMImport.com.revenuecat.purchases.kn.core.RCAdOpened
+import swiftPMImport.com.revenuecat.purchases.kn.core.RCAdRevenue
+import swiftPMImport.com.revenuecat.purchases.kn.core.RCAdRevenuePrecision
+import swiftPMImport.com.revenuecat.purchases.kn.core.RCMediatorName
+
+@ExperimentalRevenueCatApi
+public fun AdMediatorName.toIos(): RCMediatorName =
+ RCMediatorName(rawValue = value)
+
+@ExperimentalRevenueCatApi
+public fun AdRevenuePrecision.toIos(): RCAdRevenuePrecision =
+ RCAdRevenuePrecision(rawValue = value)
+
+@ExperimentalRevenueCatApi
+public fun AdFormat.toIos(): RCAdFormat =
+ RCAdFormat(rawValue = value)
+
+@ExperimentalRevenueCatApi
+public fun AdDisplayedData.toIos(): RCAdDisplayed {
+ return RCAdDisplayed(
+ networkName = networkName,
+ mediatorName = mediatorName.toIos(),
+ adFormat = adFormat.toIos(),
+ placement = placement,
+ adUnitId = adUnitId,
+ impressionId = impressionId,
+ )
+}
+
+@ExperimentalRevenueCatApi
+public fun AdOpenedData.toIos(): RCAdOpened {
+ return RCAdOpened(
+ networkName = networkName,
+ mediatorName = mediatorName.toIos(),
+ adFormat = adFormat.toIos(),
+ placement = placement,
+ adUnitId = adUnitId,
+ impressionId = impressionId,
+ )
+}
+
+@ExperimentalRevenueCatApi
+public fun AdRevenueData.toIos(): RCAdRevenue {
+ return RCAdRevenue(
+ networkName = networkName,
+ mediatorName = mediatorName.toIos(),
+ adFormat = adFormat.toIos(),
+ placement = placement,
+ adUnitId = adUnitId,
+ impressionId = impressionId,
+ revenueMicros = revenueMicros,
+ currency = currency,
+ precision = precision.toIos(),
+ )
+}
+
+@ExperimentalRevenueCatApi
+public fun AdLoadedData.toIos(): RCAdLoaded {
+ return RCAdLoaded(
+ networkName = networkName,
+ mediatorName = mediatorName.toIos(),
+ adFormat = adFormat.toIos(),
+ placement = placement,
+ adUnitId = adUnitId,
+ impressionId = impressionId,
+ )
+}
+
+@ExperimentalRevenueCatApi
+public fun AdFailedToLoadData.toIos(): RCAdFailedToLoad {
+ return RCAdFailedToLoad(
+ mediatorName = mediatorName.toIos(),
+ adFormat = adFormat.toIos(),
+ placement = placement,
+ adUnitId = adUnitId,
+ mediatorErrorCode = mediatorErrorCode?.let { NSNumber(it) },
+ )
+}
diff --git a/mappings/src/iosMain/kotlin/com/revenuecat/purchases/kmp/mappings/EntitlementVerificationMode.ios.kt b/mappings/src/iosMain/kotlin/com/revenuecat/purchases/kmp/mappings/EntitlementVerificationMode.ios.kt
new file mode 100644
index 000000000..a00e4a398
--- /dev/null
+++ b/mappings/src/iosMain/kotlin/com/revenuecat/purchases/kmp/mappings/EntitlementVerificationMode.ios.kt
@@ -0,0 +1,12 @@
+package com.revenuecat.purchases.kmp.mappings
+
+import com.revenuecat.purchases.kmp.models.EntitlementVerificationMode
+import swiftPMImport.com.revenuecat.purchases.kn.core.RCEntitlementVerificationMode
+import swiftPMImport.com.revenuecat.purchases.kn.core.RCEntitlementVerificationModeDisabled
+import swiftPMImport.com.revenuecat.purchases.kn.core.RCEntitlementVerificationModeInformational
+
+public fun EntitlementVerificationMode.toIosEntitlementVerificationMode(): RCEntitlementVerificationMode =
+ when (this) {
+ EntitlementVerificationMode.DISABLED -> RCEntitlementVerificationModeDisabled
+ EntitlementVerificationMode.INFORMATIONAL -> RCEntitlementVerificationModeInformational
+ }
diff --git a/mappings/src/iosMain/kotlin/com/revenuecat/purchases/kmp/mappings/Price.ios.kt b/mappings/src/iosMain/kotlin/com/revenuecat/purchases/kmp/mappings/Price.ios.kt
index 8308a2148..7c4f9b1b2 100644
--- a/mappings/src/iosMain/kotlin/com/revenuecat/purchases/kmp/mappings/Price.ios.kt
+++ b/mappings/src/iosMain/kotlin/com/revenuecat/purchases/kmp/mappings/Price.ios.kt
@@ -4,12 +4,12 @@ import com.revenuecat.purchases.kmp.models.Price
import platform.Foundation.NSDecimalNumber
import swiftPMImport.com.revenuecat.purchases.kn.core.RCStoreProduct
import swiftPMImport.com.revenuecat.purchases.kn.core.RCStoreProductDiscount
-import swiftPMImport.com.revenuecat.purchases.kn.core.priceAmount
+import swiftPMImport.com.revenuecat.purchases.kn.core.price
internal fun RCStoreProduct.toPrice(): Price =
Price(
formatted = localizedPriceString(),
- amountMicros = priceAmount().decimalNumberByMultiplyingByPowerOf10(6).longValue,
+ amountMicros = price().decimalNumberByMultiplyingByPowerOf10(6).longValue,
currencyCode = currencyCodeOrUsd(),
)
@@ -19,7 +19,7 @@ internal fun RCStoreProduct.currencyCodeOrUsd(): String =
internal fun RCStoreProductDiscount.toPrice(): Price =
Price(
formatted = localizedPriceString(),
- amountMicros = priceAmount().decimalNumberByMultiplyingByPowerOf10(6).longValue,
+ amountMicros = price().decimalNumberByMultiplyingByPowerOf10(6).longValue,
currencyCode = currencyCodeOrUsd(),
)
diff --git a/mappings/src/iosMain/kotlin/com/revenuecat/purchases/kmp/mappings/PurchasesAreCompletedBy.ios.kt b/mappings/src/iosMain/kotlin/com/revenuecat/purchases/kmp/mappings/PurchasesAreCompletedBy.ios.kt
new file mode 100644
index 000000000..3a02bba69
--- /dev/null
+++ b/mappings/src/iosMain/kotlin/com/revenuecat/purchases/kmp/mappings/PurchasesAreCompletedBy.ios.kt
@@ -0,0 +1,28 @@
+package com.revenuecat.purchases.kmp.mappings
+
+import com.revenuecat.purchases.kmp.models.PurchasesAreCompletedBy
+import com.revenuecat.purchases.kmp.models.StoreKitVersion
+import swiftPMImport.com.revenuecat.purchases.kn.core.RCPurchasesAreCompletedBy
+import swiftPMImport.com.revenuecat.purchases.kn.core.RCPurchasesAreCompletedByMyApp
+import swiftPMImport.com.revenuecat.purchases.kn.core.RCPurchasesAreCompletedByRevenueCat
+import swiftPMImport.com.revenuecat.purchases.kn.core.RCStoreKitVersion
+import swiftPMImport.com.revenuecat.purchases.kn.core.RCStoreKitVersion1
+import swiftPMImport.com.revenuecat.purchases.kn.core.RCStoreKitVersion2
+
+
+public fun PurchasesAreCompletedBy.toIosPurchasesAreCompletedBy(): IosPurchasesAreCompletedBy =
+ IosPurchasesAreCompletedBy(
+ purchasesAreCompletedBy = when (this) {
+ is PurchasesAreCompletedBy.RevenueCat -> RCPurchasesAreCompletedByRevenueCat
+ is PurchasesAreCompletedBy.MyApp -> RCPurchasesAreCompletedByMyApp
+ },
+ storeKitVersion = when (this) {
+ is PurchasesAreCompletedBy.RevenueCat -> RCStoreKitVersion2
+ is PurchasesAreCompletedBy.MyApp -> storeKitVersion.toIosStoreKitVersion()
+ }
+ )
+
+public data class IosPurchasesAreCompletedBy(
+ val purchasesAreCompletedBy: RCPurchasesAreCompletedBy,
+ val storeKitVersion: RCStoreKitVersion
+)
diff --git a/mappings/src/iosMain/kotlin/com/revenuecat/purchases/kmp/mappings/StoreKitVersion.ios.kt b/mappings/src/iosMain/kotlin/com/revenuecat/purchases/kmp/mappings/StoreKitVersion.ios.kt
index 3e008bf72..074685460 100644
--- a/mappings/src/iosMain/kotlin/com/revenuecat/purchases/kmp/mappings/StoreKitVersion.ios.kt
+++ b/mappings/src/iosMain/kotlin/com/revenuecat/purchases/kmp/mappings/StoreKitVersion.ios.kt
@@ -1,10 +1,13 @@
package com.revenuecat.purchases.kmp.mappings
import com.revenuecat.purchases.kmp.models.StoreKitVersion
+import swiftPMImport.com.revenuecat.purchases.kn.core.RCStoreKitVersion
+import swiftPMImport.com.revenuecat.purchases.kn.core.RCStoreKitVersion1
+import swiftPMImport.com.revenuecat.purchases.kn.core.RCStoreKitVersion2
-public fun StoreKitVersion.toHybridString(): String =
- when(this) {
- StoreKitVersion.STOREKIT_1 -> "STOREKIT_1"
- StoreKitVersion.STOREKIT_2 -> "STOREKIT_2"
- StoreKitVersion.DEFAULT -> "DEFAULT"
+public fun StoreKitVersion.toIosStoreKitVersion(): RCStoreKitVersion =
+ when (this) {
+ StoreKitVersion.STOREKIT_1 -> RCStoreKitVersion1
+ StoreKitVersion.STOREKIT_2 -> RCStoreKitVersion2
+ StoreKitVersion.DEFAULT -> RCStoreKitVersion2
}
diff --git a/mappings/src/iosMain/kotlin/com/revenuecat/purchases/kmp/mappings/StoreMessageType.ios.kt b/mappings/src/iosMain/kotlin/com/revenuecat/purchases/kmp/mappings/StoreMessageType.ios.kt
new file mode 100644
index 000000000..196ea3557
--- /dev/null
+++ b/mappings/src/iosMain/kotlin/com/revenuecat/purchases/kmp/mappings/StoreMessageType.ios.kt
@@ -0,0 +1,19 @@
+package com.revenuecat.purchases.kmp.mappings
+
+import com.revenuecat.purchases.kmp.models.StoreMessageType
+import swiftPMImport.com.revenuecat.purchases.kn.core.RCStoreMessageType
+import swiftPMImport.com.revenuecat.purchases.kn.core.RCStoreMessageTypeBillingIssue
+import swiftPMImport.com.revenuecat.purchases.kn.core.RCStoreMessageTypeGeneric
+import swiftPMImport.com.revenuecat.purchases.kn.core.RCStoreMessageTypePriceIncreaseConsent
+import swiftPMImport.com.revenuecat.purchases.kn.core.RCStoreMessageTypeWinBackOffer
+
+public fun Collection.toIosStoreMessageTypes(): Set =
+ mapTo(mutableSetOf()) { it.toIosStoreMessageType() }
+
+public fun StoreMessageType.toIosStoreMessageType(): RCStoreMessageType =
+ when (this) {
+ StoreMessageType.GENERIC -> RCStoreMessageTypeGeneric
+ StoreMessageType.BILLING_ISSUES -> RCStoreMessageTypeBillingIssue
+ StoreMessageType.PRICE_INCREASE_CONSENT -> RCStoreMessageTypePriceIncreaseConsent
+ StoreMessageType.WIN_BACK_OFFER -> RCStoreMessageTypeWinBackOffer
+ }
diff --git a/mappings/src/iosMain/kotlin/com/revenuecat/purchases/kmp/mappings/StoreProduct.ios.kt b/mappings/src/iosMain/kotlin/com/revenuecat/purchases/kmp/mappings/StoreProduct.ios.kt
index 8d6d95c24..3ecaa5d03 100644
--- a/mappings/src/iosMain/kotlin/com/revenuecat/purchases/kmp/mappings/StoreProduct.ios.kt
+++ b/mappings/src/iosMain/kotlin/com/revenuecat/purchases/kmp/mappings/StoreProduct.ios.kt
@@ -10,12 +10,12 @@ import com.revenuecat.purchases.kmp.models.StoreProduct
import com.revenuecat.purchases.kmp.models.StoreProductDiscount
import com.revenuecat.purchases.kmp.models.SubscriptionOption
import com.revenuecat.purchases.kmp.models.SubscriptionOptions
-import swiftPMImport.com.revenuecat.purchases.kn.core.pricePerMonthAmount
-import swiftPMImport.com.revenuecat.purchases.kn.core.pricePerMonthString
-import swiftPMImport.com.revenuecat.purchases.kn.core.pricePerWeekAmount
-import swiftPMImport.com.revenuecat.purchases.kn.core.pricePerWeekString
-import swiftPMImport.com.revenuecat.purchases.kn.core.pricePerYearAmount
-import swiftPMImport.com.revenuecat.purchases.kn.core.pricePerYearString
+import swiftPMImport.com.revenuecat.purchases.kn.core.localizedPricePerMonth
+import swiftPMImport.com.revenuecat.purchases.kn.core.localizedPricePerWeek
+import swiftPMImport.com.revenuecat.purchases.kn.core.localizedPricePerYear
+import swiftPMImport.com.revenuecat.purchases.kn.core.pricePerMonth
+import swiftPMImport.com.revenuecat.purchases.kn.core.pricePerWeek
+import swiftPMImport.com.revenuecat.purchases.kn.core.pricePerYear
import swiftPMImport.com.revenuecat.purchases.kn.core.RCStoreProduct as NativeIosStoreProduct
import swiftPMImport.com.revenuecat.purchases.kn.core.RCStoreProductDiscount as IosStoreProductDiscount
@@ -46,22 +46,22 @@ private class IosStoreProduct(val wrapped: NativeIosStoreProduct): StoreProduct
override val pricePerWeek: Price? by lazy {
priceOrNull(
currencyCode = price.currencyCode,
- formatted = wrapped.pricePerWeekString(),
- amountDecimal = wrapped.pricePerWeekAmount(),
+ formatted = wrapped.localizedPricePerWeek(),
+ amountDecimal = wrapped.pricePerWeek(),
)
}
override val pricePerMonth: Price? by lazy {
priceOrNull(
currencyCode = price.currencyCode,
- formatted = wrapped.pricePerMonthString(),
- amountDecimal = wrapped.pricePerMonthAmount(),
+ formatted = wrapped.localizedPricePerMonth(),
+ amountDecimal = wrapped.pricePerMonth(),
)
}
override val pricePerYear: Price? by lazy {
priceOrNull(
currencyCode = price.currencyCode,
- formatted = wrapped.pricePerYearString(),
- amountDecimal = wrapped.pricePerYearAmount(),
+ formatted = wrapped.localizedPricePerYear(),
+ amountDecimal = wrapped.pricePerYear(),
)
}
}
diff --git a/mappings/src/iosMain/kotlin/com/revenuecat/purchases/kmp/mappings/StoreTransaction.ios.kt b/mappings/src/iosMain/kotlin/com/revenuecat/purchases/kmp/mappings/StoreTransaction.ios.kt
index 29b3a66a9..13345e7d0 100644
--- a/mappings/src/iosMain/kotlin/com/revenuecat/purchases/kmp/mappings/StoreTransaction.ios.kt
+++ b/mappings/src/iosMain/kotlin/com/revenuecat/purchases/kmp/mappings/StoreTransaction.ios.kt
@@ -10,29 +10,3 @@ public fun IosStoreTransaction.toStoreTransaction(): StoreTransaction =
productIds = listOf(productIdentifier()),
purchaseTime = purchaseDate().toEpochMilliseconds(),
)
-
-public fun buildStoreTransaction(storeTransactionMap: Map): Result {
- val transactionId = storeTransactionMap["transactionIdentifier"] as? String
- val productId = storeTransactionMap["productIdentifier"] as? String
- val purchaseTime = (storeTransactionMap["purchaseDateMillis"] as? Number)?.toLong()
-
- if(transactionId == null) {
- return Result.failure(IllegalArgumentException("Expected a non-null transactionIdentifier"))
- }
-
- if(productId == null) {
- return Result.failure(IllegalArgumentException("Expected a non-null productIdentifier"))
- }
-
- if(purchaseTime == null) {
- return Result.failure(IllegalArgumentException("Expected a non-null purchaseDateMillis"))
- }
-
- return Result.success(
- StoreTransaction(
- transactionId = transactionId,
- productIds = listOf(productId),
- purchaseTime = purchaseTime
- )
- )
-}
diff --git a/mappings/src/iosTest/kotlin/com/revenuecat/purchases/kmp/mappings/StoreKitVersionTests.ios.kt b/mappings/src/iosTest/kotlin/com/revenuecat/purchases/kmp/mappings/StoreKitVersionTests.ios.kt
deleted file mode 100644
index bbaccc162..000000000
--- a/mappings/src/iosTest/kotlin/com/revenuecat/purchases/kmp/mappings/StoreKitVersionTests.ios.kt
+++ /dev/null
@@ -1,24 +0,0 @@
-package com.revenuecat.purchases.kmp.mappings
-
-import com.revenuecat.purchases.kmp.models.StoreKitVersion
-import kotlin.test.Test
-import kotlin.test.assertEquals
-
-class StoreKitVersionTests {
-
- @Test
- fun `toHybridString returns the correct values`() {
- val storeKit1 = StoreKitVersion.STOREKIT_1
- val storeKit1String = storeKit1.toHybridString()
-
- val storeKit2 = StoreKitVersion.STOREKIT_2
- val storeKit2String = storeKit2.toHybridString()
-
- val storeKitDefault = StoreKitVersion.DEFAULT
- val storeKitDefaultString = storeKitDefault.toHybridString()
-
- assertEquals("STOREKIT_1", storeKit1String)
- assertEquals("STOREKIT_2", storeKit2String)
- assertEquals("DEFAULT", storeKitDefaultString)
- }
-}
diff --git a/mappings/src/iosTest/kotlin/com/revenuecat/purchases/kmp/mappings/StoreTransactionTests.ios.kt b/mappings/src/iosTest/kotlin/com/revenuecat/purchases/kmp/mappings/StoreTransactionTests.ios.kt
deleted file mode 100644
index 28921b7ba..000000000
--- a/mappings/src/iosTest/kotlin/com/revenuecat/purchases/kmp/mappings/StoreTransactionTests.ios.kt
+++ /dev/null
@@ -1,123 +0,0 @@
-package com.revenuecat.purchases.kmp.mappings
-
-
-import kotlin.test.Test
-import kotlin.test.assertEquals
-import kotlin.test.assertTrue
-
-class StoreTransactionTests {
-
- @Test
- fun `fromMap correctly parses map with all fields`() {
- val transactionId = "someTransactionIdentifier"
- val productId = "someProductIdentifier"
- val purchaseDateMillis = 1627689600000.0
- val purchaseDate = "2021-07-31T00:00:00Z"
-
- val hybridMap: Map = mapOf(
- "transactionIdentifier" to transactionId,
- "revenueCatId" to transactionId, // Deprecated
- "productIdentifier" to productId,
- "productId" to productId, // Deprecated
- "purchaseDateMillis" to purchaseDateMillis,
- "purchaseDate" to purchaseDate
- )
-
- val result = buildStoreTransaction(storeTransactionMap = hybridMap)
-
- assertTrue(result.isSuccess)
- val storeTransaction = result.getOrThrow()
- assertEquals(transactionId, storeTransaction.transactionId)
- assertEquals(listOf(productId), storeTransaction.productIds)
- assertEquals(purchaseDateMillis.toLong(), storeTransaction.purchaseTime)
- }
-
- @Test
- fun `fromMap correctly parses map with all fields if purchaseDateMillis is an int`() {
- val transactionId = "someTransactionIdentifier"
- val productId = "someProductIdentifier"
- val purchaseDateMillis = 1627689600000
- val purchaseDate = "2021-07-31T00:00:00Z"
-
- val hybridMap: Map = mapOf(
- "transactionIdentifier" to transactionId,
- "revenueCatId" to transactionId, // Deprecated
- "productIdentifier" to productId,
- "productId" to productId, // Deprecated
- "purchaseDateMillis" to purchaseDateMillis,
- "purchaseDate" to purchaseDate
- )
-
- val result = buildStoreTransaction(storeTransactionMap = hybridMap)
-
- assertTrue(result.isSuccess)
- val storeTransaction = result.getOrThrow()
- assertEquals(transactionId, storeTransaction.transactionId)
- assertEquals(listOf(productId), storeTransaction.productIds)
- assertEquals(purchaseDateMillis, storeTransaction.purchaseTime)
- }
-
- @Test
- fun `missing transactionIdentifier returns failure`() {
- val productId = "someProductIdentifier"
- val purchaseDateMillis = 1627689600000.0
- val purchaseDate = "2021-07-31T00:00:00Z"
-
- val hybridMap: Map = mapOf(
- "productIdentifier" to productId,
- "purchaseDateMillis" to purchaseDateMillis,
- "purchaseDate" to purchaseDate
- )
-
- val result = buildStoreTransaction(storeTransactionMap = hybridMap)
-
- assertTrue(result.isFailure)
- result.exceptionOrNull()?.let {
- assertTrue(it is IllegalArgumentException)
- assertEquals("Expected a non-null transactionIdentifier", it.message)
- }
- }
-
- @Test
- fun `missing productIdentifier returns failure`() {
- val transactionId = "someTransactionIdentifier"
- val purchaseDateMillis = 1627689600000.0
- val purchaseDate = "2021-07-31T00:00:00Z"
-
- val hybridMap: Map = mapOf(
- "transactionIdentifier" to transactionId,
- "purchaseDateMillis" to purchaseDateMillis,
- "purchaseDate" to purchaseDate
- )
-
- val result = buildStoreTransaction(storeTransactionMap = hybridMap)
-
- assertTrue(result.isFailure)
- result.exceptionOrNull()?.let {
- assertTrue(it is IllegalArgumentException)
- assertEquals("Expected a non-null productIdentifier", it.message)
- }
- }
-
- @Test
- fun `missing purchaseDateMillis returns failure`() {
- val transactionId = "someTransactionIdentifier"
- val productId = "someProductIdentifier"
- val purchaseDate = "2021-07-31T00:00:00Z"
-
- val hybridMap: Map = mapOf(
- "transactionIdentifier" to transactionId,
- "productIdentifier" to productId,
- "purchaseDateMillis" to "InvalidMillis", // This should be a Double
- "purchaseDate" to purchaseDate
- )
-
- val result = buildStoreTransaction(storeTransactionMap = hybridMap)
-
- assertTrue(result.isFailure)
- result.exceptionOrNull()?.let {
- assertTrue(it is IllegalArgumentException)
- assertEquals("Expected a non-null purchaseDateMillis", it.message)
- }
- }
-}
diff --git a/migrations/3.0.0-MIGRATION.md b/migrations/3.0.0-MIGRATION.md
new file mode 100644
index 000000000..b3474e9a9
--- /dev/null
+++ b/migrations/3.0.0-MIGRATION.md
@@ -0,0 +1,36 @@
+# 3.0.0 Migration Guide
+
+This version makes integrating the SDK on iOS much easier.
+
+## Remove the native iOS SDK
+Coming from a version before `3.0.0`, your iOS project will have a dependency on RevenueCat's `PurchasesHybridCommon` and/or `PurchasesHybridCommonUI` native iOS dependencies. These should be removed. The instructions depend on how you added them.
+
+### Remove from Swift Package Manager
+If you've added `PurchasesHybridCommon` and/or `PurchasesHybridCommonUI` to your iOS project using Swift Package Manager, do the following:
+1. Open Xcode and select your project in the Project navigator.
+2. Click Package Dependencies.
+3. Select the `PurchasesHybridCommon` and/or `PurchasesHybridCommonUI` dependencies and click the '-' (minus) button to remove them.
+
+### Remove from CocoaPods
+If you've added `PurchasesHybridCommon` and/or `PurchasesHybridCommonUI` to your iOS project using CocoaPods, there are 2 possible places where these dependencies could be defined. Make sure they are removed from both.
+
+Your `Podfile` might contain lines like the following. Remove them:
+```ruby
+pod 'PurchasesHybridCommon', '17.21.2'
+pod 'PurchasesHybridCommonUI', '17.21.2'
+```
+
+Your `build.gradle[.kts]` might contain sections like the following. Remove these:
+```kotlin
+pod("PurchasesHybridCommon") {
+ version = "17.21.2"
+ extraOpts += listOf("-compiler-option", "-fmodules")
+}
+pod("PurchasesHybridCommonUI") {
+ version = "17.21.2"
+ extraOpts += listOf("-compiler-option", "-fmodules")
+}
+```
+
+## Reporting undocumented issues:
+Feel free to file an issue! [New RevenueCat Issue](https://github.com/RevenueCat/purchases-kmp/issues/new/). Make sure to state clearly that you're using version 3.0.0. Thanks!
diff --git a/models/api/models.api b/models/api/models.api
index 279cab44f..3973069f1 100644
--- a/models/api/models.api
+++ b/models/api/models.api
@@ -256,15 +256,15 @@ public final class com/revenuecat/purchases/kmp/models/OfferPaymentMode : java/l
}
public abstract interface class com/revenuecat/purchases/kmp/models/Offering {
- public abstract fun get (Ljava/lang/String;)Lcom/revenuecat/purchases/kmp/models/Package;
+ public fun get (Ljava/lang/String;)Lcom/revenuecat/purchases/kmp/models/Package;
public abstract fun getAnnual ()Lcom/revenuecat/purchases/kmp/models/Package;
public abstract fun getAvailablePackages ()Ljava/util/List;
public abstract fun getIdentifier ()Ljava/lang/String;
public abstract fun getLifetime ()Lcom/revenuecat/purchases/kmp/models/Package;
public abstract fun getMetadata ()Ljava/util/Map;
- public abstract fun getMetadataString (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
+ public fun getMetadataString (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
public abstract fun getMonthly ()Lcom/revenuecat/purchases/kmp/models/Package;
- public abstract fun getPackage (Ljava/lang/String;)Lcom/revenuecat/purchases/kmp/models/Package;
+ public fun getPackage (Ljava/lang/String;)Lcom/revenuecat/purchases/kmp/models/Package;
public abstract fun getServerDescription ()Ljava/lang/String;
public abstract fun getSixMonth ()Lcom/revenuecat/purchases/kmp/models/Package;
public abstract fun getThreeMonth ()Lcom/revenuecat/purchases/kmp/models/Package;
diff --git a/models/build.gradle.kts b/models/build.gradle.kts
index 646ca7f81..1a5e92e34 100644
--- a/models/build.gradle.kts
+++ b/models/build.gradle.kts
@@ -1,6 +1,5 @@
plugins {
id("revenuecat-library")
- alias(libs.plugins.kotlin.cocoapods)
}
revenueCat {
@@ -10,7 +9,7 @@ revenueCat {
kotlin {
sourceSets {
androidMain.dependencies {
- implementation(libs.revenuecat.common)
+ implementation(libs.revenuecat.android)
}
commonTest.dependencies {
implementation(libs.kotlin.test.annotations)
@@ -20,17 +19,6 @@ kotlin {
implementation(libs.kotlin.test.junit)
}
}
-
- cocoapods {
- version = libs.versions.revenuecat.kmp.get()
- ios.deploymentTarget = libs.versions.ios.deploymentTarget.core.get()
-
- pod("PurchasesHybridCommon") {
- version = libs.versions.revenuecat.common.get()
- extraOpts += listOf("-compiler-option", "-fmodules")
- packageName = "swiftPMImport.com.revenuecat.purchases.kn.core"
- }
- }
}
android {
diff --git a/models/models.podspec b/models/models.podspec
deleted file mode 100644
index dd32a692f..000000000
--- a/models/models.podspec
+++ /dev/null
@@ -1,54 +0,0 @@
-Pod::Spec.new do |spec|
- spec.name = 'models'
- spec.version = '2.11.0-SNAPSHOT'
- spec.homepage = ''
- spec.source = { :http=> ''}
- spec.authors = ''
- spec.license = ''
- spec.summary = ''
- spec.vendored_frameworks = 'build/cocoapods/framework/models.framework'
- spec.libraries = 'c++'
- spec.ios.deployment_target = '13.0'
- spec.dependency 'PurchasesHybridCommon', '17.55.1'
-
- if !Dir.exist?('build/cocoapods/framework/models.framework') || Dir.empty?('build/cocoapods/framework/models.framework')
- raise "
-
- Kotlin framework 'models' doesn't exist yet, so a proper Xcode project can't be generated.
- 'pod install' should be executed after running ':generateDummyFramework' Gradle task:
-
- ./gradlew :models:generateDummyFramework
-
- Alternatively, proper pod installation is performed during Gradle sync in the IDE (if Podfile location is set)"
- end
-
- spec.xcconfig = {
- 'ENABLE_USER_SCRIPT_SANDBOXING' => 'NO',
- }
-
- spec.pod_target_xcconfig = {
- 'KOTLIN_PROJECT_PATH' => ':models',
- 'PRODUCT_MODULE_NAME' => 'models',
- }
-
- spec.script_phases = [
- {
- :name => 'Build models',
- :execution_position => :before_compile,
- :shell_path => '/bin/sh',
- :script => <<-SCRIPT
- if [ "YES" = "$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED" ]; then
- echo "Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \"YES\""
- exit 0
- fi
- set -ev
- REPO_ROOT="$PODS_TARGET_SRCROOT"
- "$REPO_ROOT/../gradlew" -p "$REPO_ROOT" $KOTLIN_PROJECT_PATH:syncFramework \
- -Pkotlin.native.cocoapods.platform=$PLATFORM_NAME \
- -Pkotlin.native.cocoapods.archs="$ARCHS" \
- -Pkotlin.native.cocoapods.configuration="$CONFIGURATION"
- SCRIPT
- }
- ]
-
-end
\ No newline at end of file
diff --git a/renovate.json b/renovate.json
index 57eced641..65dffe51a 100644
--- a/renovate.json
+++ b/renovate.json
@@ -9,6 +9,15 @@
{
"matchManagers": ["git-submodules"],
"enabled": true
+ },
+ {
+ "matchPackageNames": [
+ "com.revenuecat.purchases:purchases",
+ "com.revenuecat.purchases:purchases-ui"
+ ],
+ "matchManagers": ["gradle"],
+ "enabled": true,
+ "groupName": "purchases-android"
}
]
}
diff --git a/revenuecatui/api/revenuecatui.api b/revenuecatui/api/revenuecatui.api
index 7d6850693..23a168e65 100644
--- a/revenuecatui/api/revenuecatui.api
+++ b/revenuecatui/api/revenuecatui.api
@@ -57,13 +57,13 @@ public final class com/revenuecat/purchases/kmp/ui/revenuecatui/PaywallKt {
}
public abstract interface class com/revenuecat/purchases/kmp/ui/revenuecatui/PaywallListener {
- public abstract fun onPurchaseCancelled ()V
- public abstract fun onPurchaseCompleted (Lcom/revenuecat/purchases/kmp/models/CustomerInfo;Lcom/revenuecat/purchases/kmp/models/StoreTransaction;)V
- public abstract fun onPurchaseError (Lcom/revenuecat/purchases/kmp/models/PurchasesError;)V
- public abstract fun onPurchaseStarted (Lcom/revenuecat/purchases/kmp/models/Package;)V
- public abstract fun onRestoreCompleted (Lcom/revenuecat/purchases/kmp/models/CustomerInfo;)V
- public abstract fun onRestoreError (Lcom/revenuecat/purchases/kmp/models/PurchasesError;)V
- public abstract fun onRestoreStarted ()V
+ public fun onPurchaseCancelled ()V
+ public fun onPurchaseCompleted (Lcom/revenuecat/purchases/kmp/models/CustomerInfo;Lcom/revenuecat/purchases/kmp/models/StoreTransaction;)V
+ public fun onPurchaseError (Lcom/revenuecat/purchases/kmp/models/PurchasesError;)V
+ public fun onPurchaseStarted (Lcom/revenuecat/purchases/kmp/models/Package;)V
+ public fun onRestoreCompleted (Lcom/revenuecat/purchases/kmp/models/CustomerInfo;)V
+ public fun onRestoreError (Lcom/revenuecat/purchases/kmp/models/PurchasesError;)V
+ public fun onRestoreStarted ()V
}
public final class com/revenuecat/purchases/kmp/ui/revenuecatui/PaywallListener$DefaultImpls {
diff --git a/revenuecatui/api/revenuecatui.klib.api b/revenuecatui/api/revenuecatui.klib.api
index 520299475..19a4fddad 100644
--- a/revenuecatui/api/revenuecatui.klib.api
+++ b/revenuecatui/api/revenuecatui.klib.api
@@ -21,6 +21,51 @@ abstract interface com.revenuecat.purchases.kmp.ui.revenuecatui/PaywallPurchaseL
abstract suspend fun performRestore(com.revenuecat.purchases.kmp.models/CustomerInfo): com.revenuecat.purchases.kmp.ui.revenuecatui/PurchaseLogicResult // com.revenuecat.purchases.kmp.ui.revenuecatui/PaywallPurchaseLogic.performRestore|performRestore(com.revenuecat.purchases.kmp.models.CustomerInfo){}[0]
}
+abstract class com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue { // com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue|null[0]
+ final class Boolean : com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue { // com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue.Boolean|null[0]
+ constructor (kotlin/Boolean) // com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue.Boolean.|(kotlin.Boolean){}[0]
+
+ final val value // com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue.Boolean.value|{}value[0]
+ final fun (): kotlin/Boolean // com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue.Boolean.value.|(){}[0]
+
+ final fun equals(kotlin/Any?): kotlin/Boolean // com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue.Boolean.equals|equals(kotlin.Any?){}[0]
+ final fun hashCode(): kotlin/Int // com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue.Boolean.hashCode|hashCode(){}[0]
+ final fun toString(): kotlin/String // com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue.Boolean.toString|toString(){}[0]
+ }
+
+ final class Number : com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue { // com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue.Number|null[0]
+ constructor (kotlin/Double) // com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue.Number.|(kotlin.Double){}[0]
+ constructor (kotlin/Float) // com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue.Number.|(kotlin.Float){}[0]
+ constructor (kotlin/Int) // com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue.Number.|(kotlin.Int){}[0]
+ constructor (kotlin/Long) // com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue.Number.|(kotlin.Long){}[0]
+
+ final val value // com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue.Number.value|{}value[0]
+ final fun (): kotlin/Double // com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue.Number.value.|(){}[0]
+
+ final fun equals(kotlin/Any?): kotlin/Boolean // com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue.Number.equals|equals(kotlin.Any?){}[0]
+ final fun hashCode(): kotlin/Int // com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue.Number.hashCode|hashCode(){}[0]
+ final fun toString(): kotlin/String // com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue.Number.toString|toString(){}[0]
+ }
+
+ final class String : com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue { // com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue.String|null[0]
+ constructor (kotlin/String) // com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue.String.|(kotlin.String){}[0]
+
+ final val value // com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue.String.value|{}value[0]
+ final fun (): kotlin/String // com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue.String.value.|(){}[0]
+
+ final fun equals(kotlin/Any?): kotlin/Boolean // com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue.String.equals|equals(kotlin.Any?){}[0]
+ final fun hashCode(): kotlin/Int // com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue.String.hashCode|hashCode(){}[0]
+ final fun toString(): kotlin/String // com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue.String.toString|toString(){}[0]
+ }
+
+ final object Companion { // com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue.Companion|null[0]
+ final fun boolean(kotlin/Boolean): com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue // com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue.Companion.boolean|boolean(kotlin.Boolean){}[0]
+ final fun number(kotlin/Double): com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue // com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue.Companion.number|number(kotlin.Double){}[0]
+ final fun number(kotlin/Int): com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue // com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue.Companion.number|number(kotlin.Int){}[0]
+ final fun string(kotlin/String): com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue // com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue.Companion.string|string(kotlin.String){}[0]
+ }
+}
+
abstract class com.revenuecat.purchases.kmp.ui.revenuecatui/PurchaseLogicResult { // com.revenuecat.purchases.kmp.ui.revenuecatui/PurchaseLogicResult|null[0]
final class Cancellation : com.revenuecat.purchases.kmp.ui.revenuecatui/PurchaseLogicResult { // com.revenuecat.purchases.kmp.ui.revenuecatui/PurchaseLogicResult.Cancellation|null[0]
constructor () // com.revenuecat.purchases.kmp.ui.revenuecatui/PurchaseLogicResult.Cancellation.|(){}[0]
@@ -85,59 +130,10 @@ final class com.revenuecat.purchases.kmp.ui.revenuecatui/PaywallPurchaseLogicPar
final fun (): com.revenuecat.purchases.kmp.models/Package // com.revenuecat.purchases.kmp.ui.revenuecatui/PaywallPurchaseLogicParams.rcPackage.|(){}[0]
}
-abstract class com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue { // com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue|null[0]
- final class Boolean : com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue { // com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue.Boolean|null[0]
- constructor (kotlin/Boolean) // com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue.Boolean.|(kotlin.Boolean){}[0]
-
- final val value // com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue.Boolean.value|{}value[0]
- final fun (): kotlin/Boolean // com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue.Boolean.value.|(){}[0]
-
- final fun equals(kotlin/Any?): kotlin/Boolean // com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue.Boolean.equals|equals(kotlin.Any?){}[0]
- final fun hashCode(): kotlin/Int // com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue.Boolean.hashCode|hashCode(){}[0]
- final fun toString(): kotlin/String // com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue.Boolean.toString|toString(){}[0]
- }
-
- final class Number : com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue { // com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue.Number|null[0]
- constructor (kotlin/Double) // com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue.Number.|(kotlin.Double){}[0]
- constructor (kotlin/Float) // com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue.Number.|(kotlin.Float){}[0]
- constructor (kotlin/Int) // com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue.Number.|(kotlin.Int){}[0]
- constructor (kotlin/Long) // com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue.Number.|(kotlin.Long){}[0]
-
- final val value // com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue.Number.value|{}value[0]
- final fun (): kotlin/Double // com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue.Number.value.|(){}[0]
-
- final fun equals(kotlin/Any?): kotlin/Boolean // com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue.Number.equals|equals(kotlin.Any?){}[0]
- final fun hashCode(): kotlin/Int // com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue.Number.hashCode|hashCode(){}[0]
- final fun toString(): kotlin/String // com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue.Number.toString|toString(){}[0]
- }
-
- final class String : com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue { // com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue.String|null[0]
- constructor (kotlin/String) // com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue.String.|(kotlin.String){}[0]
-
- final val value // com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue.String.value|{}value[0]
- final fun (): kotlin/String // com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue.String.value.|(){}[0]
-
- final fun equals(kotlin/Any?): kotlin/Boolean // com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue.String.equals|equals(kotlin.Any?){}[0]
- final fun hashCode(): kotlin/Int // com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue.String.hashCode|hashCode(){}[0]
- final fun toString(): kotlin/String // com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue.String.toString|toString(){}[0]
- }
-
- final object Companion { // com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue.Companion|null[0]
- final fun boolean(kotlin/Boolean): com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue // com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue.Companion.boolean|boolean(kotlin.Boolean){}[0]
- final fun number(kotlin/Double): com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue // com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue.Companion.number|number(kotlin.Double){}[0]
- final fun number(kotlin/Int): com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue // com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue.Companion.number|number(kotlin.Int){}[0]
- final fun string(kotlin/String): com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue // com.revenuecat.purchases.kmp.ui.revenuecatui/CustomVariableValue.Companion.string|string(kotlin.String){}[0]
- }
-}
-
-final val com.revenuecat.purchases.kmp.ui.revenuecatui.modifier/com_revenuecat_purchases_kmp_ui_revenuecatui_modifier_LayoutViewControllerState$stableprop // com.revenuecat.purchases.kmp.ui.revenuecatui.modifier/com_revenuecat_purchases_kmp_ui_revenuecatui_modifier_LayoutViewControllerState$stableprop|#static{}com_revenuecat_purchases_kmp_ui_revenuecatui_modifier_LayoutViewControllerState$stableprop[0]
final val com.revenuecat.purchases.kmp.ui.revenuecatui/com_revenuecat_purchases_kmp_ui_revenuecatui_CustomVariableValue$stableprop // com.revenuecat.purchases.kmp.ui.revenuecatui/com_revenuecat_purchases_kmp_ui_revenuecatui_CustomVariableValue$stableprop|#static{}com_revenuecat_purchases_kmp_ui_revenuecatui_CustomVariableValue$stableprop[0]
final val com.revenuecat.purchases.kmp.ui.revenuecatui/com_revenuecat_purchases_kmp_ui_revenuecatui_CustomVariableValue_Boolean$stableprop // com.revenuecat.purchases.kmp.ui.revenuecatui/com_revenuecat_purchases_kmp_ui_revenuecatui_CustomVariableValue_Boolean$stableprop|#static{}com_revenuecat_purchases_kmp_ui_revenuecatui_CustomVariableValue_Boolean$stableprop[0]
final val com.revenuecat.purchases.kmp.ui.revenuecatui/com_revenuecat_purchases_kmp_ui_revenuecatui_CustomVariableValue_Number$stableprop // com.revenuecat.purchases.kmp.ui.revenuecatui/com_revenuecat_purchases_kmp_ui_revenuecatui_CustomVariableValue_Number$stableprop|#static{}com_revenuecat_purchases_kmp_ui_revenuecatui_CustomVariableValue_Number$stableprop[0]
final val com.revenuecat.purchases.kmp.ui.revenuecatui/com_revenuecat_purchases_kmp_ui_revenuecatui_CustomVariableValue_String$stableprop // com.revenuecat.purchases.kmp.ui.revenuecatui/com_revenuecat_purchases_kmp_ui_revenuecatui_CustomVariableValue_String$stableprop|#static{}com_revenuecat_purchases_kmp_ui_revenuecatui_CustomVariableValue_String$stableprop[0]
-final val com.revenuecat.purchases.kmp.ui.revenuecatui/com_revenuecat_purchases_kmp_ui_revenuecatui_IosCustomerCenterDelegate$stableprop // com.revenuecat.purchases.kmp.ui.revenuecatui/com_revenuecat_purchases_kmp_ui_revenuecatui_IosCustomerCenterDelegate$stableprop|#static{}com_revenuecat_purchases_kmp_ui_revenuecatui_IosCustomerCenterDelegate$stableprop[0]
-final val com.revenuecat.purchases.kmp.ui.revenuecatui/com_revenuecat_purchases_kmp_ui_revenuecatui_IosPaywallDelegate$stableprop // com.revenuecat.purchases.kmp.ui.revenuecatui/com_revenuecat_purchases_kmp_ui_revenuecatui_IosPaywallDelegate$stableprop|#static{}com_revenuecat_purchases_kmp_ui_revenuecatui_IosPaywallDelegate$stableprop[0]
-final val com.revenuecat.purchases.kmp.ui.revenuecatui/com_revenuecat_purchases_kmp_ui_revenuecatui_IosPaywallProxyDelegate$stableprop // com.revenuecat.purchases.kmp.ui.revenuecatui/com_revenuecat_purchases_kmp_ui_revenuecatui_IosPaywallProxyDelegate$stableprop|#static{}com_revenuecat_purchases_kmp_ui_revenuecatui_IosPaywallProxyDelegate$stableprop[0]
final val com.revenuecat.purchases.kmp.ui.revenuecatui/com_revenuecat_purchases_kmp_ui_revenuecatui_PaywallOptions$stableprop // com.revenuecat.purchases.kmp.ui.revenuecatui/com_revenuecat_purchases_kmp_ui_revenuecatui_PaywallOptions$stableprop|#static{}com_revenuecat_purchases_kmp_ui_revenuecatui_PaywallOptions$stableprop[0]
final val com.revenuecat.purchases.kmp.ui.revenuecatui/com_revenuecat_purchases_kmp_ui_revenuecatui_PaywallOptions_Builder$stableprop // com.revenuecat.purchases.kmp.ui.revenuecatui/com_revenuecat_purchases_kmp_ui_revenuecatui_PaywallOptions_Builder$stableprop|#static{}com_revenuecat_purchases_kmp_ui_revenuecatui_PaywallOptions_Builder$stableprop[0]
final val com.revenuecat.purchases.kmp.ui.revenuecatui/com_revenuecat_purchases_kmp_ui_revenuecatui_PaywallPurchaseLogicParams$stableprop // com.revenuecat.purchases.kmp.ui.revenuecatui/com_revenuecat_purchases_kmp_ui_revenuecatui_PaywallPurchaseLogicParams$stableprop|#static{}com_revenuecat_purchases_kmp_ui_revenuecatui_PaywallPurchaseLogicParams$stableprop[0]
@@ -146,7 +142,6 @@ final val com.revenuecat.purchases.kmp.ui.revenuecatui/com_revenuecat_purchases_
final val com.revenuecat.purchases.kmp.ui.revenuecatui/com_revenuecat_purchases_kmp_ui_revenuecatui_PurchaseLogicResult_Error$stableprop // com.revenuecat.purchases.kmp.ui.revenuecatui/com_revenuecat_purchases_kmp_ui_revenuecatui_PurchaseLogicResult_Error$stableprop|#static{}com_revenuecat_purchases_kmp_ui_revenuecatui_PurchaseLogicResult_Error$stableprop[0]
final val com.revenuecat.purchases.kmp.ui.revenuecatui/com_revenuecat_purchases_kmp_ui_revenuecatui_PurchaseLogicResult_Success$stableprop // com.revenuecat.purchases.kmp.ui.revenuecatui/com_revenuecat_purchases_kmp_ui_revenuecatui_PurchaseLogicResult_Success$stableprop|#static{}com_revenuecat_purchases_kmp_ui_revenuecatui_PurchaseLogicResult_Success$stableprop[0]
-final fun com.revenuecat.purchases.kmp.ui.revenuecatui.modifier/com_revenuecat_purchases_kmp_ui_revenuecatui_modifier_LayoutViewControllerState$stableprop_getter(): kotlin/Int // com.revenuecat.purchases.kmp.ui.revenuecatui.modifier/com_revenuecat_purchases_kmp_ui_revenuecatui_modifier_LayoutViewControllerState$stableprop_getter|com_revenuecat_purchases_kmp_ui_revenuecatui_modifier_LayoutViewControllerState$stableprop_getter(){}[0]
final fun com.revenuecat.purchases.kmp.ui.revenuecatui/CustomerCenter(androidx.compose.ui/Modifier?, kotlin/Function0, androidx.compose.runtime/Composer?, kotlin/Int, kotlin/Int) // com.revenuecat.purchases.kmp.ui.revenuecatui/CustomerCenter|CustomerCenter(androidx.compose.ui.Modifier?;kotlin.Function0;androidx.compose.runtime.Composer?;kotlin.Int;kotlin.Int){}[0]
final fun com.revenuecat.purchases.kmp.ui.revenuecatui/OriginalTemplatePaywallFooter(com.revenuecat.purchases.kmp.ui.revenuecatui/PaywallOptions, kotlin/Function3?, androidx.compose.runtime/Composer?, kotlin/Int, kotlin/Int) // com.revenuecat.purchases.kmp.ui.revenuecatui/OriginalTemplatePaywallFooter|OriginalTemplatePaywallFooter(com.revenuecat.purchases.kmp.ui.revenuecatui.PaywallOptions;kotlin.Function3?;androidx.compose.runtime.Composer?;kotlin.Int;kotlin.Int){}[0]
final fun com.revenuecat.purchases.kmp.ui.revenuecatui/Paywall(com.revenuecat.purchases.kmp.ui.revenuecatui/PaywallOptions, androidx.compose.runtime/Composer?, kotlin/Int) // com.revenuecat.purchases.kmp.ui.revenuecatui/Paywall|Paywall(com.revenuecat.purchases.kmp.ui.revenuecatui.PaywallOptions;androidx.compose.runtime.Composer?;kotlin.Int){}[0]
@@ -156,9 +151,6 @@ final fun com.revenuecat.purchases.kmp.ui.revenuecatui/com_revenuecat_purchases_
final fun com.revenuecat.purchases.kmp.ui.revenuecatui/com_revenuecat_purchases_kmp_ui_revenuecatui_CustomVariableValue_Boolean$stableprop_getter(): kotlin/Int // com.revenuecat.purchases.kmp.ui.revenuecatui/com_revenuecat_purchases_kmp_ui_revenuecatui_CustomVariableValue_Boolean$stableprop_getter|com_revenuecat_purchases_kmp_ui_revenuecatui_CustomVariableValue_Boolean$stableprop_getter(){}[0]
final fun com.revenuecat.purchases.kmp.ui.revenuecatui/com_revenuecat_purchases_kmp_ui_revenuecatui_CustomVariableValue_Number$stableprop_getter(): kotlin/Int // com.revenuecat.purchases.kmp.ui.revenuecatui/com_revenuecat_purchases_kmp_ui_revenuecatui_CustomVariableValue_Number$stableprop_getter|com_revenuecat_purchases_kmp_ui_revenuecatui_CustomVariableValue_Number$stableprop_getter(){}[0]
final fun com.revenuecat.purchases.kmp.ui.revenuecatui/com_revenuecat_purchases_kmp_ui_revenuecatui_CustomVariableValue_String$stableprop_getter(): kotlin/Int // com.revenuecat.purchases.kmp.ui.revenuecatui/com_revenuecat_purchases_kmp_ui_revenuecatui_CustomVariableValue_String$stableprop_getter|com_revenuecat_purchases_kmp_ui_revenuecatui_CustomVariableValue_String$stableprop_getter(){}[0]
-final fun com.revenuecat.purchases.kmp.ui.revenuecatui/com_revenuecat_purchases_kmp_ui_revenuecatui_IosCustomerCenterDelegate$stableprop_getter(): kotlin/Int // com.revenuecat.purchases.kmp.ui.revenuecatui/com_revenuecat_purchases_kmp_ui_revenuecatui_IosCustomerCenterDelegate$stableprop_getter|com_revenuecat_purchases_kmp_ui_revenuecatui_IosCustomerCenterDelegate$stableprop_getter(){}[0]
-final fun com.revenuecat.purchases.kmp.ui.revenuecatui/com_revenuecat_purchases_kmp_ui_revenuecatui_IosPaywallDelegate$stableprop_getter(): kotlin/Int // com.revenuecat.purchases.kmp.ui.revenuecatui/com_revenuecat_purchases_kmp_ui_revenuecatui_IosPaywallDelegate$stableprop_getter|com_revenuecat_purchases_kmp_ui_revenuecatui_IosPaywallDelegate$stableprop_getter(){}[0]
-final fun com.revenuecat.purchases.kmp.ui.revenuecatui/com_revenuecat_purchases_kmp_ui_revenuecatui_IosPaywallProxyDelegate$stableprop_getter(): kotlin/Int // com.revenuecat.purchases.kmp.ui.revenuecatui/com_revenuecat_purchases_kmp_ui_revenuecatui_IosPaywallProxyDelegate$stableprop_getter|com_revenuecat_purchases_kmp_ui_revenuecatui_IosPaywallProxyDelegate$stableprop_getter(){}[0]
final fun com.revenuecat.purchases.kmp.ui.revenuecatui/com_revenuecat_purchases_kmp_ui_revenuecatui_PaywallOptions$stableprop_getter(): kotlin/Int // com.revenuecat.purchases.kmp.ui.revenuecatui/com_revenuecat_purchases_kmp_ui_revenuecatui_PaywallOptions$stableprop_getter|com_revenuecat_purchases_kmp_ui_revenuecatui_PaywallOptions$stableprop_getter(){}[0]
final fun com.revenuecat.purchases.kmp.ui.revenuecatui/com_revenuecat_purchases_kmp_ui_revenuecatui_PaywallOptions_Builder$stableprop_getter(): kotlin/Int // com.revenuecat.purchases.kmp.ui.revenuecatui/com_revenuecat_purchases_kmp_ui_revenuecatui_PaywallOptions_Builder$stableprop_getter|com_revenuecat_purchases_kmp_ui_revenuecatui_PaywallOptions_Builder$stableprop_getter(){}[0]
final fun com.revenuecat.purchases.kmp.ui.revenuecatui/com_revenuecat_purchases_kmp_ui_revenuecatui_PaywallPurchaseLogicParams$stableprop_getter(): kotlin/Int // com.revenuecat.purchases.kmp.ui.revenuecatui/com_revenuecat_purchases_kmp_ui_revenuecatui_PaywallPurchaseLogicParams$stableprop_getter|com_revenuecat_purchases_kmp_ui_revenuecatui_PaywallPurchaseLogicParams$stableprop_getter(){}[0]
diff --git a/revenuecatui/build.gradle.kts b/revenuecatui/build.gradle.kts
index dfd6ffd50..6a2238bfd 100644
--- a/revenuecatui/build.gradle.kts
+++ b/revenuecatui/build.gradle.kts
@@ -2,7 +2,6 @@ plugins {
id("revenuecat-library")
alias(libs.plugins.jetbrains.compose)
alias(libs.plugins.compose.compiler)
- alias(libs.plugins.kotlin.cocoapods)
}
revenueCat {
@@ -20,24 +19,15 @@ kotlin {
implementation(compose.components.uiToolingPreview)
}
androidMain.dependencies {
- implementation(libs.revenuecat.commonUi)
+ implementation(libs.revenuecatUi.android)
implementation(projects.mappings)
}
iosMain.dependencies {
+ implementation(projects.knCore)
+ implementation(projects.knUi)
implementation(projects.mappings)
}
}
-
- cocoapods {
- version = libs.versions.revenuecat.kmp.get()
- ios.deploymentTarget = libs.versions.ios.deploymentTarget.ui.get()
-
- pod("PurchasesHybridCommonUI") {
- version = libs.versions.revenuecat.common.get()
- extraOpts += listOf("-compiler-option", "-fmodules")
- packageName = "swiftPMImport.com.revenuecat.purchases.kn.ui"
- }
- }
}
android {
diff --git a/revenuecatui/revenuecatui.podspec b/revenuecatui/revenuecatui.podspec
deleted file mode 100644
index 4cdf16d49..000000000
--- a/revenuecatui/revenuecatui.podspec
+++ /dev/null
@@ -1,54 +0,0 @@
-Pod::Spec.new do |spec|
- spec.name = 'revenuecatui'
- spec.version = '2.11.0-SNAPSHOT'
- spec.homepage = ''
- spec.source = { :http=> ''}
- spec.authors = ''
- spec.license = ''
- spec.summary = ''
- spec.vendored_frameworks = 'build/cocoapods/framework/revenuecatui.framework'
- spec.libraries = 'c++'
- spec.ios.deployment_target = '15.0'
- spec.dependency 'PurchasesHybridCommonUI', '17.55.1'
-
- if !Dir.exist?('build/cocoapods/framework/revenuecatui.framework') || Dir.empty?('build/cocoapods/framework/revenuecatui.framework')
- raise "
-
- Kotlin framework 'revenuecatui' doesn't exist yet, so a proper Xcode project can't be generated.
- 'pod install' should be executed after running ':generateDummyFramework' Gradle task:
-
- ./gradlew :revenuecatui:generateDummyFramework
-
- Alternatively, proper pod installation is performed during Gradle sync in the IDE (if Podfile location is set)"
- end
-
- spec.xcconfig = {
- 'ENABLE_USER_SCRIPT_SANDBOXING' => 'NO',
- }
-
- spec.pod_target_xcconfig = {
- 'KOTLIN_PROJECT_PATH' => ':revenuecatui',
- 'PRODUCT_MODULE_NAME' => 'revenuecatui',
- }
-
- spec.script_phases = [
- {
- :name => 'Build revenuecatui',
- :execution_position => :before_compile,
- :shell_path => '/bin/sh',
- :script => <<-SCRIPT
- if [ "YES" = "$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED" ]; then
- echo "Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \"YES\""
- exit 0
- fi
- set -ev
- REPO_ROOT="$PODS_TARGET_SRCROOT"
- "$REPO_ROOT/../gradlew" -p "$REPO_ROOT" $KOTLIN_PROJECT_PATH:syncFramework \
- -Pkotlin.native.cocoapods.platform=$PLATFORM_NAME \
- -Pkotlin.native.cocoapods.archs="$ARCHS" \
- -Pkotlin.native.cocoapods.configuration="$CONFIGURATION"
- SCRIPT
- }
- ]
- spec.resources = ['build/compose/cocoapods/compose-resources']
-end
\ No newline at end of file
diff --git a/revenuecatui/src/iosMain/kotlin/com/revenuecat/purchases/kmp/ui/revenuecatui/PaywallOptionsKtx.kt b/revenuecatui/src/iosMain/kotlin/com/revenuecat/purchases/kmp/ui/revenuecatui/PaywallOptionsKtx.kt
index 9cb9804f9..850c6354a 100644
--- a/revenuecatui/src/iosMain/kotlin/com/revenuecat/purchases/kmp/ui/revenuecatui/PaywallOptionsKtx.kt
+++ b/revenuecatui/src/iosMain/kotlin/com/revenuecat/purchases/kmp/ui/revenuecatui/PaywallOptionsKtx.kt
@@ -11,14 +11,14 @@ import kotlinx.cinterop.pointed
import platform.CoreGraphics.CGSize
import platform.Foundation.NSError
import platform.darwin.NSObject
-import swiftPMImport.com.revenuecat.purchases.kn.ui.RCCustomerInfo
-import swiftPMImport.com.revenuecat.purchases.kn.ui.RCPackage
+import swiftPMImport.com.revenuecat.purchases.kn.core.RCCustomerInfo
+import swiftPMImport.com.revenuecat.purchases.kn.core.RCPackage
+import swiftPMImport.com.revenuecat.purchases.kn.core.RCStoreTransaction
import swiftPMImport.com.revenuecat.purchases.kn.ui.RCPaywallViewController
import swiftPMImport.com.revenuecat.purchases.kn.ui.RCPaywallViewControllerDelegateProtocol
-import swiftPMImport.com.revenuecat.purchases.kn.ui.RCStoreTransaction
-import swiftPMImport.com.revenuecat.purchases.kn.core.RCCustomerInfo as PhcCustomerInfo
-import swiftPMImport.com.revenuecat.purchases.kn.core.RCPackage as PhcPackage
-import swiftPMImport.com.revenuecat.purchases.kn.core.RCStoreTransaction as PhcStoreTransaction
+import swiftPMImport.com.revenuecat.purchases.kn.ui.RCCustomerInfo as RCCustomerInfoFromKnUi
+import swiftPMImport.com.revenuecat.purchases.kn.ui.RCPackage as RCPackageFromKnUi
+import swiftPMImport.com.revenuecat.purchases.kn.ui.RCStoreTransaction as RCStoreTransactionFromKnUi
internal class IosPaywallDelegate(
private val listener: PaywallListener?,
@@ -29,22 +29,22 @@ internal class IosPaywallDelegate(
@Suppress("CAST_NEVER_SUCCEEDS")
override fun paywallViewController(
controller: RCPaywallViewController,
- didStartPurchaseWithPackage: RCPackage
+ didStartPurchaseWithPackage: RCPackageFromKnUi,
) {
listener?.onPurchaseStarted(
- (didStartPurchaseWithPackage as PhcPackage).toPackage()
+ (didStartPurchaseWithPackage as RCPackage).toPackage()
)
}
@Suppress("CAST_NEVER_SUCCEEDS")
override fun paywallViewController(
controller: RCPaywallViewController,
- didFinishPurchasingWithCustomerInfo: RCCustomerInfo,
- transaction: RCStoreTransaction?
+ didFinishPurchasingWithCustomerInfo: RCCustomerInfoFromKnUi,
+ transaction: RCStoreTransactionFromKnUi?,
) {
listener?.onPurchaseCompleted(
- (didFinishPurchasingWithCustomerInfo as PhcCustomerInfo).toCustomerInfo(),
- (transaction as PhcStoreTransaction).toStoreTransaction()
+ (didFinishPurchasingWithCustomerInfo as RCCustomerInfo).toCustomerInfo(),
+ (transaction as RCStoreTransaction).toStoreTransaction()
)
}
@@ -68,10 +68,10 @@ internal class IosPaywallDelegate(
@Suppress("CAST_NEVER_SUCCEEDS")
override fun paywallViewController(
controller: RCPaywallViewController,
- didFinishRestoringWithCustomerInfo: RCCustomerInfo
+ didFinishRestoringWithCustomerInfo: RCCustomerInfoFromKnUi
) {
listener?.onRestoreCompleted(
- (didFinishRestoringWithCustomerInfo as PhcCustomerInfo).toCustomerInfo()
+ (didFinishRestoringWithCustomerInfo as RCCustomerInfo).toCustomerInfo()
)
}
diff --git a/revenuecatui/src/iosMain/kotlin/com/revenuecat/purchases/kmp/ui/revenuecatui/UIKitCustomerCenter.kt b/revenuecatui/src/iosMain/kotlin/com/revenuecat/purchases/kmp/ui/revenuecatui/UIKitCustomerCenter.kt
index d57407664..a7b3dc943 100644
--- a/revenuecatui/src/iosMain/kotlin/com/revenuecat/purchases/kmp/ui/revenuecatui/UIKitCustomerCenter.kt
+++ b/revenuecatui/src/iosMain/kotlin/com/revenuecat/purchases/kmp/ui/revenuecatui/UIKitCustomerCenter.kt
@@ -4,8 +4,8 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.viewinterop.UIKitViewController
-import swiftPMImport.com.revenuecat.purchases.kn.ui.CustomerCenterUIViewController
-import swiftPMImport.com.revenuecat.purchases.kn.ui.RCCustomerCenterViewControllerDelegateWrapperProtocol
+import swiftPMImport.com.revenuecat.purchases.kn.ui.RCCustomerCenterViewControllerDelegateProtocol
+import swiftPMImport.com.revenuecat.purchases.kn.ui.RCCustomerCenterViewController
import com.revenuecat.purchases.kmp.ui.revenuecatui.modifier.layoutViewController
import com.revenuecat.purchases.kmp.ui.revenuecatui.modifier.rememberLayoutViewControllerState
import platform.darwin.NSObject
@@ -23,11 +23,8 @@ internal fun UIKitCustomerCenter(
UIKitViewController(
modifier = modifier.layoutViewController(layoutViewControllerState),
factory = {
- CustomerCenterUIViewController()
- .apply {
- setDelegate(delegate)
- setOnCloseHandler(onDismiss)
- }.also {
+ RCCustomerCenterViewController(delegate = delegate)
+ .also {
layoutViewControllerState.setViewController(it)
}
},
@@ -38,11 +35,11 @@ internal fun UIKitCustomerCenter(
)
}
-internal class IosCustomerCenterDelegate(
+private class IosCustomerCenterDelegate(
private val onDismiss: () -> Unit
-) : RCCustomerCenterViewControllerDelegateWrapperProtocol, NSObject() {
+) : RCCustomerCenterViewControllerDelegateProtocol, NSObject() {
- override fun customerCenterViewControllerWasDismissed(controller: CustomerCenterUIViewController) {
+ override fun customerCenterViewControllerWasDismissed(controller: RCCustomerCenterViewController) {
onDismiss()
}
}
diff --git a/revenuecatui/src/iosMain/kotlin/com/revenuecat/purchases/kmp/ui/revenuecatui/UIKitPaywall.kt b/revenuecatui/src/iosMain/kotlin/com/revenuecat/purchases/kmp/ui/revenuecatui/UIKitPaywall.kt
index 1187cdb7d..17ff259fc 100644
--- a/revenuecatui/src/iosMain/kotlin/com/revenuecat/purchases/kmp/ui/revenuecatui/UIKitPaywall.kt
+++ b/revenuecatui/src/iosMain/kotlin/com/revenuecat/purchases/kmp/ui/revenuecatui/UIKitPaywall.kt
@@ -1,35 +1,31 @@
package com.revenuecat.purchases.kmp.ui.revenuecatui
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.viewinterop.UIKitViewController
-import com.revenuecat.purchases.kmp.mappings.toIosOffering
-import com.revenuecat.purchases.kmp.ui.revenuecatui.modifier.layoutViewController
-import com.revenuecat.purchases.kmp.ui.revenuecatui.modifier.rememberLayoutViewControllerState
import com.revenuecat.purchases.kmp.Purchases
+import com.revenuecat.purchases.kmp.ktx.awaitCustomerInfo
+import com.revenuecat.purchases.kmp.mappings.toIosOffering
+import com.revenuecat.purchases.kmp.mappings.toPackage
import com.revenuecat.purchases.kmp.models.CustomerInfo
-import com.revenuecat.purchases.kmp.models.Package
import com.revenuecat.purchases.kmp.models.PurchasesError
import com.revenuecat.purchases.kmp.models.PurchasesErrorCode
-import kotlinx.cinterop.CValue
-import kotlinx.cinterop.ObjCSignatureOverride
-import kotlinx.cinterop.memScoped
-import kotlinx.cinterop.pointed
+import com.revenuecat.purchases.kmp.ui.revenuecatui.modifier.layoutViewController
+import com.revenuecat.purchases.kmp.ui.revenuecatui.modifier.rememberLayoutViewControllerState
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
-import kotlinx.coroutines.suspendCancellableCoroutine
-import platform.CoreGraphics.CGSize
+import platform.Foundation.NSError
+import platform.Foundation.NSLocalizedDescriptionKey
import platform.darwin.NSObject
-import swiftPMImport.com.revenuecat.purchases.kn.ui.HybridPurchaseLogicBridge
-import swiftPMImport.com.revenuecat.purchases.kn.ui.PaywallProxy
-import swiftPMImport.com.revenuecat.purchases.kn.ui.PaywallViewCreationParams
-import swiftPMImport.com.revenuecat.purchases.kn.ui.RCOffering
+import swiftPMImport.com.revenuecat.purchases.kn.core.RCPackage
import swiftPMImport.com.revenuecat.purchases.kn.ui.RCPaywallFooterViewController
+import swiftPMImport.com.revenuecat.purchases.kn.ui.RCPaywallPurchaseHandlerProtocol
import swiftPMImport.com.revenuecat.purchases.kn.ui.RCPaywallViewController
-import swiftPMImport.com.revenuecat.purchases.kn.ui.RCPaywallViewControllerDelegateWrapperProtocol
+import kotlin.coroutines.cancellation.CancellationException
+import swiftPMImport.com.revenuecat.purchases.kn.ui.RCOffering as RCOfferingFromKnUi
+import swiftPMImport.com.revenuecat.purchases.kn.ui.RCPackage as RCPackageFromKnUi
@Composable
internal fun UIKitPaywall(
@@ -38,83 +34,39 @@ internal fun UIKitPaywall(
modifier: Modifier = Modifier,
) {
val layoutViewControllerState = rememberLayoutViewControllerState()
-
- if (options.purchaseLogic != null && footer) {
- println("[RevenueCat] Warning: purchaseLogic is not supported with PaywallFooter on iOS. " +
- "The custom purchase logic will be ignored.")
- }
-
- // PaywallProxy is used for all non-footer paywalls (with or without custom purchase logic).
- // It creates the VC and sets itself as its delegate (weak ref), so retain it here.
val coroutineScope = rememberCoroutineScope()
- val proxy = remember { if (!footer) PaywallProxy() else null }
- val purchaseLogicBridge = remember(options.purchaseLogic, options.offering) {
- if (!footer) {
- options.purchaseLogic?.toHybridPurchaseLogicBridge(
- packages = options.offering?.availablePackages.orEmpty(),
- scope = coroutineScope,
- )
- } else null
- }
- val proxyDelegate = remember(options.listener, options.dismissRequest) {
- if (!footer) {
- IosPaywallProxyDelegate(
- listener = options.listener,
- dismissRequest = options.dismissRequest,
- onHeightChange = { layoutViewControllerState.updateIntrinsicContentSize() },
- )
- } else null
- }
- proxy?.setDelegate(proxyDelegate)
- // Clean up bridge pending requests when the composable leaves composition.
- DisposableEffect(purchaseLogicBridge) {
- onDispose {
- purchaseLogicBridge?.cancelPending()
- }
- }
-
- // Footer path still uses direct VC constructors since PaywallProxy doesn't support
- // PaywallViewCreationParams for footer views.
+ // Keeping references to avoid them being deallocated.
val dismissRequestedHandler: (RCPaywallViewController?) -> Unit =
remember(options.dismissRequest) { { options.dismissRequest() } }
- val footerDelegate = remember(options.listener) {
- if (footer) {
- IosPaywallDelegate(
- listener = options.listener,
- onHeightChange = { layoutViewControllerState.updateIntrinsicContentSize() }
- )
- } else null
+ val delegate = remember(options.listener) {
+ IosPaywallDelegate(
+ listener = options.listener,
+ onHeightChange = { layoutViewControllerState.updateIntrinsicContentSize() }
+ )
}
UIKitViewController(
modifier = modifier.layoutViewController(layoutViewControllerState),
factory = {
- if (!footer && proxy != null) {
- val params = PaywallViewCreationParams().apply {
- purchaseLogicBridge?.let { setPurchaseLogicBridge(it) }
- setDisplayCloseButton(
- platform.Foundation.NSNumber(bool = options.shouldDisplayDismissButton)
- )
- if (options.customVariables.isNotEmpty()) {
- setCustomVariables(options.customVariables.toIosCustomVariables())
- }
- }
- val offering = options.offering?.toIosOffering()
- if (offering != null) {
- params.setOfferingIdentifier(offering.identifier())
- }
- proxy.createPaywallViewWithParams(params)
- .also { layoutViewControllerState.setViewController(it) }
- } else {
- RCPaywallFooterViewController(
- offering = options.offering?.toIosOffering() as? RCOffering,
- displayCloseButton = options.shouldDisplayDismissButton,
- shouldBlockTouchEvents = false,
- dismissRequestedHandler = dismissRequestedHandler,
- ).apply { setDelegate(footerDelegate) }
- .also { layoutViewControllerState.setViewController(it) }
- }
+ val paywallViewController = if (footer) RCPaywallFooterViewController(
+ offering = options.offering?.toIosOffering() as? RCOfferingFromKnUi,
+ displayCloseButton = options.shouldDisplayDismissButton,
+ shouldBlockTouchEvents = false,
+ dismissRequestedHandler = dismissRequestedHandler,
+ purchaseHandler = options.purchaseLogic?.toPurchaseHandler(coroutineScope),
+ ) else RCPaywallViewController(
+ offering = options.offering?.toIosOffering() as? RCOfferingFromKnUi,
+ displayCloseButton = options.shouldDisplayDismissButton,
+ shouldBlockTouchEvents = false,
+ dismissRequestedHandler = dismissRequestedHandler,
+ purchaseHandler = options.purchaseLogic?.toPurchaseHandler(coroutineScope),
+ )
+
+ paywallViewController
+ .apply { setDelegate(delegate) }
+ .apply { setCustomVariables(options.customVariables) }
+ .also { layoutViewControllerState.setViewController(it) }
},
properties = uiKitInteropPropertiesNonExperimental(
nonCooperativeInteractionMode = true,
@@ -123,165 +75,86 @@ internal fun UIKitPaywall(
)
}
-/**
- * Delegate for the [PaywallProxy] path. Implements the dictionary-based
- * [RCPaywallViewControllerDelegateWrapperProtocol] to receive events from the proxy.
- * Handles dismiss requests, height changes, and forwards simple listener events.
- */
-internal class IosPaywallProxyDelegate(
- private val listener: PaywallListener?,
- private val dismissRequest: () -> Unit,
- private val onHeightChange: (Int) -> Unit,
-) : RCPaywallViewControllerDelegateWrapperProtocol,
- NSObject() {
-
- override fun paywallViewControllerRequestedDismissal(
- controller: RCPaywallViewController
- ) {
- dismissRequest()
- }
-
- override fun paywallViewControllerWasDismissed(controller: RCPaywallViewController) {
- // Cleanup is handled by PaywallProxy internally.
- }
-
- override fun paywallViewControllerDidStartPurchase(controller: RCPaywallViewController) {
- // onPurchaseStarted requires a Package object that we don't have in the proxy path.
- }
-
- override fun paywallViewControllerDidCancelPurchase(controller: RCPaywallViewController) {
- listener?.onPurchaseCancelled()
- }
-
- @ObjCSignatureOverride
- @Suppress("CONFLICTING_OVERLOADS", "PARAMETER_NAME_CHANGED_ON_OVERRIDE")
- override fun paywallViewController(
- controller: RCPaywallViewController,
- didFailPurchasingWithErrorDictionary: Map
- ) {
- listener?.onPurchaseError(didFailPurchasingWithErrorDictionary.toPurchasesError())
+private fun RCPaywallViewController.setCustomVariables(variables: Map) {
+ variables.forEach { (key, value) ->
+ when (value) {
+ is CustomVariableValue.String -> setCustomVariable(value = value.value, forKey = key)
+ is CustomVariableValue.Number -> setCustomVariableNumber(
+ value = value.value,
+ forKey = key
+ )
+ is CustomVariableValue.Boolean -> setCustomVariableBool(
+ value = value.value,
+ forKey = key
+ )
+ else -> error("Unknown CustomVariableValue type: ${value::class.simpleName}")
+ }
}
+}
- override fun paywallViewControllerDidStartRestore(controller: RCPaywallViewController) {
- listener?.onRestoreStarted()
- }
+private fun PaywallPurchaseLogic.toPurchaseHandler(
+ coroutineScope: CoroutineScope,
+) = PurchaseHandler(this, coroutineScope) {
+ Purchases.sharedInstance.awaitCustomerInfo()
+}
- @ObjCSignatureOverride
- @Suppress("CONFLICTING_OVERLOADS", "PARAMETER_NAME_CHANGED_ON_OVERRIDE")
- override fun paywallViewController(
- controller: RCPaywallViewController,
- didFailRestoringWithErrorDictionary: Map
- ) {
- listener?.onRestoreError(didFailRestoringWithErrorDictionary.toPurchasesError())
+private class PurchaseHandler(
+ private val purchaseLogic: PaywallPurchaseLogic,
+ private val coroutineScope: CoroutineScope,
+ private val getCustomerInfo: suspend () -> CustomerInfo,
+) : RCPaywallPurchaseHandlerProtocol, NSObject() {
+
+ override fun performRestoreWithCompletion(completion: (success: Boolean, error: NSError?) -> Unit) {
+ coroutineScope.launch {
+ var success = false
+ var error: NSError? = null
+ try {
+ val result = purchaseLogic.performRestore(getCustomerInfo())
+ error = (result as? PurchaseLogicResult.Error)?.errorDetails?.toNSError()
+ success = result is PurchaseLogicResult.Success
+ } catch (e: CancellationException) {
+ throw e
+ } catch (t: Throwable) {
+ error = PurchasesError(
+ code = PurchasesErrorCode.UnknownError,
+ underlyingErrorMessage = "Error performing restore: ${t.message}"
+ ).toNSError()
+ } finally {
+ completion(success, error)
+ }
+ }
}
- override fun paywallViewController(
- controller: RCPaywallViewController,
- didChangeSizeTo: CValue
+ override fun performPurchaseFor(
+ `package`: RCPackageFromKnUi,
+ completion: (userCancelled: Boolean, error: NSError?) -> Unit
) {
- onHeightChange(didChangeSizeTo.heightInt())
- }
-}
-
-private fun PaywallPurchaseLogic.toHybridPurchaseLogicBridge(
- packages: List,
- scope: CoroutineScope,
-): HybridPurchaseLogicBridge {
- val purchaseLogic = this
- val packagesByIdentifier = packages.associateBy { it.identifier }
-
- return HybridPurchaseLogicBridge(
- onPerformPurchase = { eventData ->
- eventData.launchBridgeRequest(scope) { requestId ->
- @Suppress("UNCHECKED_CAST")
- val packageDict = eventData?.get(HybridPurchaseLogicBridge.eventKeyPackageBeingPurchased())
- as? Map
- val packageIdentifier = packageDict?.get("identifier") as? String
- val rcPackage = packageIdentifier?.let { packagesByIdentifier[it] }
- ?: error("Unable to find package with identifier: $packageIdentifier")
- val params = PaywallPurchaseLogicParams(
- rcPackage = rcPackage,
+ coroutineScope.launch {
+ var userCancelled = false
+ var error: NSError? = null
+ try {
+ val result = purchaseLogic.performPurchase(
+ PaywallPurchaseLogicParams((`package` as RCPackage).toPackage())
)
- purchaseLogic.performPurchase(params)
- }
- },
- onPerformRestore = { eventData ->
- eventData.launchBridgeRequest(scope) {
- val customerInfo = fetchCustomerInfo()
- purchaseLogic.performRestore(customerInfo)
+ error = (result as? PurchaseLogicResult.Error)?.errorDetails?.toNSError()
+ userCancelled = result is PurchaseLogicResult.Cancellation
+ } catch (e: CancellationException) {
+ userCancelled = true
+ throw e
+ } catch (t: Throwable) {
+ error = PurchasesError(
+ code = PurchasesErrorCode.UnknownError,
+ underlyingErrorMessage = "Error performing purchase: ${t.message}"
+ ).toNSError()
+ } finally {
+ completion(userCancelled, error)
}
}
- )
-}
-
-/**
- * Extracts the request ID from bridge event data, launches a coroutine to execute [block],
- * and resolves the result (or error) back to the bridge.
- */
-private fun Map?.launchBridgeRequest(
- scope: CoroutineScope,
- block: suspend (requestId: String) -> PurchaseLogicResult,
-) {
- val requestId = this?.get(HybridPurchaseLogicBridge.eventKeyRequestId()) as? String ?: return
- scope.launch {
- try {
- resolveResult(requestId, block(requestId))
- } catch (e: Exception) {
- HybridPurchaseLogicBridge.resolveResultWithRequestId(
- requestId = requestId,
- resultString = HybridPurchaseLogicBridge.resultError(),
- errorMessage = e.message,
- )
- }
}
-}
-private fun resolveResult(requestId: String, result: PurchaseLogicResult) {
- when (result) {
- is PurchaseLogicResult.Success -> HybridPurchaseLogicBridge.resolveResultWithRequestId(
- requestId = requestId,
- resultString = HybridPurchaseLogicBridge.resultSuccess(),
- )
- is PurchaseLogicResult.Cancellation -> HybridPurchaseLogicBridge.resolveResultWithRequestId(
- requestId = requestId,
- resultString = HybridPurchaseLogicBridge.resultCancellation(),
- )
- is PurchaseLogicResult.Error -> HybridPurchaseLogicBridge.resolveResultWithRequestId(
- requestId = requestId,
- resultString = HybridPurchaseLogicBridge.resultError(),
- errorMessage = result.errorDetails?.let {
- it.underlyingErrorMessage ?: it.code.description
- },
- )
- }
+ private fun PurchasesError.toNSError() = NSError(
+ code = code.code.toLong(),
+ domain = "com.revenuecat.purchases.kmp",
+ userInfo = mapOf(NSLocalizedDescriptionKey to message),
+ )
}
-
-private suspend fun fetchCustomerInfo(): CustomerInfo =
- suspendCancellableCoroutine { continuation ->
- Purchases.sharedInstance.getCustomerInfo(
- onError = { error ->
- continuation.resumeWith(Result.failure(Exception(error.message)))
- },
- onSuccess = { customerInfo ->
- continuation.resumeWith(Result.success(customerInfo))
- },
- )
- }
-
-private fun Map.toPurchasesError(): PurchasesError = PurchasesError(
- code = PurchasesErrorCode.UnknownError,
- underlyingErrorMessage = this["message"] as? String,
-)
-
-private fun CValue.heightInt(): Int =
- memScoped { ptr.pointed.height.toInt() }
-
-private fun Map.toIosCustomVariables(): Map =
- entries.associate { (key, value) ->
- key to when (value) {
- is CustomVariableValue.String -> value.value as Any
- is CustomVariableValue.Number -> value.value as Any
- is CustomVariableValue.Boolean -> value.value as Any
- else -> error("Unknown CustomVariableValue type: ${value::class.simpleName}")
- }
- }
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 23b60066a..d6c19c572 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -21,8 +21,9 @@ rootProject.name = "purchases-kmp"
include(":apiTester")
include(":composeApp")
include(":core")
-include(":datetime")
include(":either")
+include(":kn-core")
+include(":kn-ui")
include(":mappings")
include(":models")
include(":result")