Skip to content

Commit

Permalink
Adds Paywalls (#77)
Browse files Browse the repository at this point in the history
* Adds a new :ui module.

* Renames :ui to :paywalls.
Apparently :ui conflicts with compose:ui, causing compile errors.

* Increases heap space to 4 GiB.

* Uses the kobankat-library plugin, and sets the package to ui.revenuecatui for consistency.

* Adds the correct ui dependencies.

* Adds PaywallOptions.

* Adds Paywall.

* Adds PaywallFooter.

* Minimizes dependencies.

* Fixes restoring purchases on Android.

* Fixes toAndroidPaywallOptions().

* Fixes the minSdk for paywalls.

* The sample app can now show paywalls.

* Adds footer paywall buttons.

* Fixes showing footer paywalls.

* Adds the RevenueCatUI pod to iosApp.

* Fixes "unrecognized selector sent to class" by using the correct (PHC) pods in iosApp.

* Removes useless receiver.

* Updates to Kotlin 2.0.0. Can revert later if needed.

* Adds Kotlin code to the Xcode workspace.

* Makes platform-to-common error conversion public. Could be made internal later, with an annotation?

* Adds .kotlin to .gitignore.

* Adds PaywallListener.

* Adds PaywallListener to PaywallOptions.

* Adds shouldDisplayDismissButton to PaywallOptions.

* Adds and uses UIKitPaywall.

* Propagates shouldDisplayDismissButton on Android.

* Deletes PaywallViewControllerDelegate.

* Sets shouldDisplayDismissButton to true.

* Updates some Xcode files.

* Adds a back button to CustomPaywallContent.

* Fixes the height of PaywallFooter by animating it.

* PaywallFooter now also works without a listener set.

* UIKitPaywall uses UIKitViewController instead of UIKitView.

* Adds docs to PaywallListener to fix Detekt.

* Updates public api dump.

* Fixes api-tester.

* Adds 2 run configurations that mimic CI.

* Adds public api dump for paywalls.
  • Loading branch information
JayShortway authored Jun 7, 2024
1 parent 7e39f42 commit 4918256
Show file tree
Hide file tree
Showing 41 changed files with 1,123 additions and 134 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@ captures
.cxx
local.properties
xcuserdata
Pods
Pods
.kotlin
!.run
25 changes: 25 additions & 0 deletions .run/kobankat [_apiTester_assemble].run.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="kobankat [:apiTester:assemble]"
type="GradleRunConfiguration" factoryName="Gradle">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value=":apiTester:assemble" />
</list>
</option>
<option name="vmOptions" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>false</RunAsTest>
<method v="2" />
</configuration>
</component>
25 changes: 25 additions & 0 deletions .run/kobankat [detektCommonMain].run.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="kobankat [detektCommonMain]" type="GradleRunConfiguration"
factoryName="Gradle">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="detektCommonMain" />
</list>
</option>
<option name="vmOptions" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>false</RunAsTest>
<method v="2" />
</configuration>
</component>
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,11 @@ import io.shortway.kobankat.models.SubscriptionOptions
import io.shortway.kobankat.models.defaultOption
import io.shortway.kobankat.models.description
import io.shortway.kobankat.models.discounts
import io.shortway.kobankat.models.formattedPricePerMonth
import io.shortway.kobankat.models.id
import io.shortway.kobankat.models.introductoryDiscount
import io.shortway.kobankat.models.period
import io.shortway.kobankat.models.presentedOfferingContext
import io.shortway.kobankat.models.price
import io.shortway.kobankat.models.pricePerMonth
import io.shortway.kobankat.models.pricePerWeek
import io.shortway.kobankat.models.pricePerYear
import io.shortway.kobankat.models.purchasingData
import io.shortway.kobankat.models.subscriptionOptions
import io.shortway.kobankat.models.title
Expand All @@ -35,14 +31,6 @@ private class StoreProductAPI {
val storeProductId: String = id
val type: ProductType = type
val price: Price = price
val formattedPricePerMonth: String? = formattedPricePerMonth(locale)
val formattedPricePerMonthNoLocale: String? = formattedPricePerMonth()
val pricePerWeek: Price? = pricePerWeek(locale)
val pricePerMonth: Price? = pricePerMonth(locale)
val pricePerYear: Price? = pricePerYear(locale)
val pricePerWeekNoLocale: Price? = pricePerYear()
val pricePerMonthNoLocale: Price? = pricePerMonth()
val pricePerYearNoLocale: Price? = pricePerYear()
val title: String = title
val description: String? = description
val period: Period? = period
Expand Down
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ plugins {
alias(libs.plugins.android.application).apply(false)
alias(libs.plugins.android.library).apply(false)
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)
Expand Down
4 changes: 3 additions & 1 deletion composeApp/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ plugins {
alias(libs.plugins.kotlin.multiplatform)
alias(libs.plugins.android.application)
alias(libs.plugins.jetbrains.compose)
alias(libs.plugins.compose.compiler)
}

kotlin {
Expand Down Expand Up @@ -45,6 +46,7 @@ kotlin {
implementation(projects.result)
implementation(projects.either)
implementation(projects.datetime)
implementation(projects.paywalls)
}
androidMain.dependencies {
implementation(libs.androidx.compose.ui.tooling.preview)
Expand All @@ -63,7 +65,7 @@ android {

defaultConfig {
applicationId = "io.shortway.kobankat.sample"
minSdk = libs.versions.android.minSdk.get().toInt()
minSdk = 24
targetSdk = libs.versions.android.targetSdk.get().toInt()
versionCode = 1
versionName = "1.0"
Expand Down
100 changes: 48 additions & 52 deletions composeApp/src/commonMain/kotlin/io/shortway/kobankat/sample/App.kt
Original file line number Diff line number Diff line change
@@ -1,87 +1,83 @@
package io.shortway.kobankat.sample

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.Button
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.unit.dp
import io.shortway.kobankat.LogLevel
import io.shortway.kobankat.PurchasesConfiguration
import io.shortway.kobankat.PurchasesFactory
import androidx.compose.ui.text.style.TextAlign
import io.shortway.kobankat.Offering
import io.shortway.kobankat.ui.revenuecatui.Paywall
import io.shortway.kobankat.ui.revenuecatui.PaywallFooter
import io.shortway.kobankat.ui.revenuecatui.PaywallOptions

@Composable
fun App() {
val logs = remember { mutableStateListOf<String>() }
LaunchedEffect(Unit) {
// In a real app, you'd probably have a class dedicated to app initialization logic.
PurchasesFactory.configure(
PurchasesConfiguration(
apiKey = "YOUR-REVENUECAT-API-KEY",
)
)
PurchasesFactory.logLevel = LogLevel.VERBOSE
PurchasesFactory.logHandler = SimpleLogHandler { logs.add(it) }
}

MaterialTheme {
Column(
modifier = Modifier
.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "KobanKat logs",
modifier = Modifier.padding(all = 16.dp),
style = MaterialTheme.typography.h4
)
var showPaywallAsFooter by remember { mutableStateOf(false) }
var paywallOffering: Offering? by remember { mutableStateOf(null) }

if (paywallOffering == null) MainScreen(
onShowPaywallClick = { offering, footer ->
showPaywallAsFooter = footer
paywallOffering = offering
},
modifier = Modifier.fillMaxSize()
) else {
val options = PaywallOptions(dismissRequest = { paywallOffering = null }) {
offering = paywallOffering
shouldDisplayDismissButton = true
}

if (showPaywallAsFooter) PaywallFooter(options) { contentPadding ->
CustomPaywallContent(
onBackClick = { paywallOffering = null },
modifier = Modifier
.fillMaxSize()
.background(Color.Magenta)
.padding(contentPadding),
)
}
else Paywall(options)
}

LogView(
logs = logs,
modifier = Modifier
.weight(1f)
.background(Color.LightGray),
)
}
}
}

@Composable
private fun LogView(
logs: SnapshotStateList<String>,
private fun CustomPaywallContent(
onBackClick: () -> Unit,
modifier: Modifier = Modifier,
) {
val listState = rememberLazyListState()
LaunchedEffect(logs.size) {
listState.animateScrollToItem(index = logs.lastIndex.coerceAtLeast(0))
}

LazyColumn(
Column(
modifier = modifier,
state = listState,
contentPadding = PaddingValues(all = 16.dp)
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
items(logs) {
Text(
text = it,
modifier = Modifier.padding(vertical = 4.dp),
fontFamily = FontFamily.Monospace,
style = MaterialTheme.typography.caption
)
Text(
text = "Custom paywall content!",
textAlign = TextAlign.Center,
style = MaterialTheme.typography.h4,
)
Button(onClick = onBackClick) {
Text("Go back")
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package io.shortway.kobankat.sample

import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.unit.dp

@Composable
internal fun LogView(
logs: SnapshotStateList<String>,
modifier: Modifier = Modifier,
) {
val listState = rememberLazyListState()
LaunchedEffect(logs.size) {
listState.animateScrollToItem(index = logs.lastIndex.coerceAtLeast(0))
}

LazyColumn(
modifier = modifier,
state = listState,
contentPadding = PaddingValues(all = 16.dp)
) {
items(logs) {
Text(
text = it,
modifier = Modifier.padding(vertical = 4.dp),
fontFamily = FontFamily.Monospace,
style = MaterialTheme.typography.caption
)
}
}
}
Loading

0 comments on commit 4918256

Please sign in to comment.