diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index e4b6c63c0..4d31b69ec 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -36,6 +36,7 @@ androidx-xr-scenecore = "1.0.0-alpha10"
androidxHiltNavigationCompose = "1.3.0"
appcompat = "1.7.1"
arcorePlayServices = "1.0.0-alpha09"
+playbilling = "8.3.0"
coil = "2.7.0"
# @keep
compileSdk = "36"
@@ -98,6 +99,7 @@ wearToolingPreview = "1.0.0"
webkit = "1.14.0"
wearPhoneInteractions = "1.1.0"
wearRemoteInteractions = "1.1.0"
+runtime = "1.10.0"
[libraries]
accompanist-adaptive = "com.google.accompanist:accompanist-adaptive:0.37.3"
@@ -202,6 +204,7 @@ androidx-xr-arcore = { module = "androidx.xr.arcore:arcore", version.ref = "andr
androidx-xr-compose = { module = "androidx.xr.compose:compose", version.ref = "androidx-xr-compose" }
androidx-xr-scenecore = { module = "androidx.xr.scenecore:scenecore", version.ref = "androidx-xr-scenecore" }
appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" }
+billing = { module = "com.android.billingclient:billing", version.ref = "playbilling" }
coil-kt-compose = { module = "io.coil-kt:coil-compose", version.ref = "coil" }
compose-foundation = { module = "androidx.wear.compose:compose-foundation", version.ref = "wearComposeFoundation" }
compose-ui-tooling = { module = "androidx.wear.compose:compose-ui-tooling", version.ref = "composeUiTooling" }
@@ -241,6 +244,7 @@ wear-compose-material = { module = "androidx.wear.compose:compose-material", ver
wear-compose-material3 = { module = "androidx.wear.compose:compose-material3", version.ref = "wearComposeMaterial3" }
androidx-wear-phone-interactions = { group = "androidx.wear", name = "wear-phone-interactions", version.ref = "wearPhoneInteractions" }
androidx-wear-remote-interactions = { group = "androidx.wear", name = "wear-remote-interactions", version.ref = "wearRemoteInteractions" }
+androidx-runtime = { group = "androidx.compose.runtime", name = "runtime", version.ref = "runtime" }
[plugins]
android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" }
diff --git a/playbilling/.gitignore b/playbilling/.gitignore
new file mode 100644
index 000000000..42afabfd2
--- /dev/null
+++ b/playbilling/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/playbilling/build.gradle b/playbilling/build.gradle
new file mode 100644
index 000000000..27cba0bd7
--- /dev/null
+++ b/playbilling/build.gradle
@@ -0,0 +1,44 @@
+plugins {
+ alias(libs.plugins.android.application)
+}
+
+android {
+ namespace 'com.example.pbl'
+ compileSdk {
+ version = release(36) {
+ minorApiLevel = 1
+ }
+ }
+
+ defaultConfig {
+ applicationId "com.example.pbl"
+ minSdk 24
+ targetSdk 36
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_17
+ targetCompatibility JavaVersion.VERSION_17
+ }
+}
+
+dependencies {
+ implementation libs.guava
+ implementation libs.billing
+ implementation libs.androidx.appcompat
+ implementation libs.google.android.material
+ implementation libs.androidx.runtime
+ testImplementation libs.junit
+ androidTestImplementation libs.androidx.test.ext.junit
+ androidTestImplementation libs.androidx.test.espresso.core
+}
\ No newline at end of file
diff --git a/playbilling/proguard-rules.pro b/playbilling/proguard-rules.pro
new file mode 100644
index 000000000..481bb4348
--- /dev/null
+++ b/playbilling/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/playbilling/src/androidTest/java/com/example/pbl/ExampleInstrumentedTest.java b/playbilling/src/androidTest/java/com/example/pbl/ExampleInstrumentedTest.java
new file mode 100644
index 000000000..14f1893fc
--- /dev/null
+++ b/playbilling/src/androidTest/java/com/example/pbl/ExampleInstrumentedTest.java
@@ -0,0 +1,26 @@
+package com.example.pbl;
+
+import android.content.Context;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see Testing documentation
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+ @Test
+ public void useAppContext() {
+ // Context of the app under test.
+ Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ assertEquals("com.example.pbl", appContext.getPackageName());
+ }
+}
\ No newline at end of file
diff --git a/playbilling/src/main/AndroidManifest.xml b/playbilling/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..0d77fa244
--- /dev/null
+++ b/playbilling/src/main/AndroidManifest.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
diff --git a/playbilling/src/main/java/com/example/pbl/BillingClientWrapper.java b/playbilling/src/main/java/com/example/pbl/BillingClientWrapper.java
new file mode 100644
index 000000000..cb7a8d81b
--- /dev/null
+++ b/playbilling/src/main/java/com/example/pbl/BillingClientWrapper.java
@@ -0,0 +1,273 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.pbl;
+
+import android.app.Activity;
+import android.content.Context;
+
+import com.android.billingclient.api.AcknowledgePurchaseParams;
+import com.android.billingclient.api.BillingClient;
+import com.android.billingclient.api.BillingClient.BillingResponseCode;
+import com.android.billingclient.api.BillingClient.ProductType;
+import com.android.billingclient.api.BillingClientStateListener;
+import com.android.billingclient.api.BillingFlowParams;
+import com.android.billingclient.api.BillingFlowParams.ProductDetailsParams;
+import com.android.billingclient.api.BillingFlowParams.SubscriptionUpdateParams;
+import com.android.billingclient.api.BillingResult;
+import com.android.billingclient.api.ConsumeParams;
+import com.android.billingclient.api.ConsumeResponseListener;
+import com.android.billingclient.api.GetBillingConfigParams;
+import com.android.billingclient.api.InAppMessageParams;
+import com.android.billingclient.api.InAppMessageResult;
+import com.android.billingclient.api.PendingPurchasesParams;
+import com.android.billingclient.api.ProductDetails;
+import com.android.billingclient.api.Purchase;
+import com.android.billingclient.api.PurchasesUpdatedListener;
+import com.android.billingclient.api.QueryProductDetailsParams;
+import com.google.common.collect.ImmutableList;
+
+import java.util.List;
+
+/**
+ * A wrapper for the Google Play Billing Library that handles all the billing logic.
+ */
+class BillingClientWrapper {
+
+ private final Context context;
+ private final Activity activity;
+ private final BillingClient billingClient;
+
+ private List productDetailsList;
+ private ProductDetails productDetails;
+
+ public BillingClientWrapper(Context context, Activity activity) {
+ this.context = context;
+ this.activity = activity;
+
+ // [START android_playbilling_initialize_java]
+ // 1. Initialize the BillingClient.
+ PurchasesUpdatedListener purchasesUpdatedListener =
+ (billingResult, purchases) -> {
+ // To be implemented in a later section.
+ if (billingResult.getResponseCode() == BillingResponseCode.OK && purchases != null) {
+ for (Purchase purchase : (List) purchases) {
+ // Process the purchase.
+ }
+ } else if (billingResult.getResponseCode() == BillingResponseCode.USER_CANCELED) {
+ // Handle an error caused by a user canceling the purchase flow.
+ } else {
+ // Handle any other error codes.
+ }
+ };
+
+ this.billingClient =
+ BillingClient.newBuilder(context)
+ .setListener(purchasesUpdatedListener)
+ // [START android_playbilling_enableautoreconnect_java]
+ .enablePendingPurchases(PendingPurchasesParams.newBuilder()
+ .enableOneTimeProducts()
+ .build())
+ // [END android_playbilling_enableautoreconnect_java]
+ .build();
+ // [END android_playbilling_initialize_java]
+
+ startConnection();
+ }
+
+ public void startConnection() {
+ // [START android_playbilling_startconnection_java]
+ billingClient.startConnection(
+ new BillingClientStateListener() {
+ @Override
+ public void onBillingSetupFinished(BillingResult billingResult) {
+ if (billingResult.getResponseCode() == BillingResponseCode.OK) {
+ // The BillingClient is ready. You can query purchases here.
+ // It's a good practice to query products after the connection is established.
+ queryProductDetails();
+ }
+ }
+
+ @Override
+ public void onBillingServiceDisconnected() {
+ // Try to restart the connection on the next request to
+ // Google Play by calling the startConnection() method.
+ // This is automatically handled by the library when you call a method that requires a connection.
+ }
+ });
+ // [END android_playbilling_startconnection_java]
+ }
+
+ public void queryProductDetails() {
+ // [START android_playbilling_queryproductdetails_java]
+ QueryProductDetailsParams queryProductDetailsParams =
+ QueryProductDetailsParams.newBuilder()
+ .setProductList(
+ ImmutableList.of(
+ QueryProductDetailsParams.Product.newBuilder()
+ .setProductId("product_id_example")
+ .setProductType(ProductType.SUBS)
+ .build()))
+ .build();
+
+ billingClient.queryProductDetailsAsync(
+ queryProductDetailsParams,
+ (billingResult, fetchedProductDetailsList) -> {
+ if (billingResult.getResponseCode() == BillingResponseCode.OK && fetchedProductDetailsList != null) {
+ this.productDetailsList = fetchedProductDetailsList.getProductDetailsList();
+ // Now that the list is populated, you can use it.
+ // For example, find a specific product.
+ if (!this.productDetailsList.isEmpty()) {
+ this.productDetails = this.productDetailsList.get(0);
+ // Any methods that require productDetails should be called from here.
+ }
+ }
+ });
+ // [END android_playbilling_queryproductdetails_java]
+ }
+
+ public void consumeProduct(Purchase purchase) {
+ // [START android_playbilling_consumeproduct_java]
+ ConsumeParams consumeParams =
+ ConsumeParams.newBuilder().setPurchaseToken(purchase.getPurchaseToken()).build();
+
+ ConsumeResponseListener listener =
+ (billingResult, purchaseToken) -> {
+ if (billingResult.getResponseCode() == BillingResponseCode.OK) {
+ // Handle the success of the consume operation.
+ }
+ };
+
+ billingClient.consumeAsync(consumeParams, listener);
+ // [END android_playbilling_consumeproduct_java]
+ }
+
+ public void acknowledgePurchase(Purchase purchase) {
+ // [START android_playbilling_acknowledge_java]
+ if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) {
+ if (!purchase.isAcknowledged()) {
+ AcknowledgePurchaseParams acknowledgePurchaseParams =
+ AcknowledgePurchaseParams.newBuilder()
+ .setPurchaseToken(purchase.getPurchaseToken())
+ .build();
+ billingClient.acknowledgePurchase(acknowledgePurchaseParams, (billingResult) -> {
+ // Acknowledgment handled.
+ });
+ }
+ }
+ // [END android_playbilling_acknowledge_java]
+ }
+
+ public void getBillingConfigAsync() {
+ // [START android_playbilling_getbillingconfig_java]
+ GetBillingConfigParams getBillingConfigParams = GetBillingConfigParams.newBuilder().build();
+ billingClient.getBillingConfigAsync(
+ getBillingConfigParams,
+ (billingResult, billingConfig) -> {
+ if (billingResult.getResponseCode() == BillingResponseCode.OK && billingConfig != null) {
+ String countryCode = billingConfig.getCountryCode();
+ } else {
+ // TODO: Handle errors
+ }
+ });
+ // [END android_playbilling_getbillingconfig_java]
+ }
+
+ public void changeSubscriptionPlan() {
+ if (productDetails == null) {
+ // Can't launch the flow if product details aren't loaded yet.
+ // You could initiate a query here or show an error message.
+ return;
+ }
+ String purchaseTokenOfExistingSubscription = "purchase_token";
+ // [START android_playbilling_subscription_replace_java]
+ ProductDetailsParams productDetailsParams =
+ ProductDetailsParams.newBuilder()
+ .setProductDetails(productDetails)
+ .build();
+
+ BillingFlowParams billingFlowParams =
+ BillingFlowParams.newBuilder()
+ .setSubscriptionUpdateParams(
+ SubscriptionUpdateParams.newBuilder()
+ .setOldPurchaseToken(purchaseTokenOfExistingSubscription)
+ .build())
+ .setProductDetailsParamsList(ImmutableList.of(productDetailsParams))
+ .build();
+
+ billingClient.launchBillingFlow(activity, billingFlowParams);
+ // [END android_playbilling_subscription_replace_java]
+ }
+
+ public void changeSubscriptionPlanDeprecated() {
+ if (productDetails == null || productDetails.getSubscriptionOfferDetails() == null || productDetails.getSubscriptionOfferDetails().isEmpty()) {
+ // Can't launch the flow if product details or offers aren't loaded yet.
+ return;
+ }
+
+ int selectedOfferIndex = 0;
+ // [START android_playbilling_subscription_update_deprecated_java]
+ String offerToken =
+ productDetails.getSubscriptionOfferDetails().get(selectedOfferIndex).getOfferToken();
+
+ BillingFlowParams billingFlowParams =
+ BillingFlowParams.newBuilder()
+ .setProductDetailsParamsList(
+ ImmutableList.of(
+ ProductDetailsParams.newBuilder()
+ // fetched via queryProductDetailsAsync
+ .setProductDetails(productDetails)
+ // offerToken can be found in
+ // ProductDetails=>SubscriptionOfferDetails
+ .setOfferToken(offerToken)
+ .build()))
+ .setSubscriptionUpdateParams(
+ SubscriptionUpdateParams.newBuilder()
+ // purchaseToken can be found in Purchase#getPurchaseToken
+ .setOldPurchaseToken("old_purchase_token")
+ .setSubscriptionReplacementMode(
+ SubscriptionUpdateParams.ReplacementMode.CHARGE_FULL_PRICE)
+ .build())
+ .build();
+
+ BillingResult billingResult = billingClient.launchBillingFlow(activity, billingFlowParams);
+ // ...
+ // [END android_playbilling_subscription_update_deprecated_java]
+ }
+
+ public void inAppMessaging() {
+ // [START android_playbilling_inappmessaging_java]
+ InAppMessageParams inAppMessageParams =
+ InAppMessageParams.newBuilder()
+ .addInAppMessageCategoryToShow(
+ InAppMessageParams.InAppMessageCategoryId.TRANSACTIONAL)
+ .build();
+ billingClient.showInAppMessages(
+ activity,
+ inAppMessageParams,
+ (inAppMessageResult) -> {
+ if (inAppMessageResult.getResponseCode()
+ == InAppMessageResult.InAppMessageResponseCode.NO_ACTION_NEEDED) {
+ // an in-app message was already displayed within the last day
+ }
+ });
+ // [END android_playbilling_inappmessaging_java]
+ }
+
+ // Unused methods from the original file have been removed for clarity
+ // (e.g., onPurchasesUpdated, handlePurchaseRecap) as their logic is
+ // now integrated into the PurchasesUpdatedListener in the constructor.
+}
diff --git a/playbilling/src/main/res/drawable/ic_launcher_background.xml b/playbilling/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 000000000..da4cfc866
--- /dev/null
+++ b/playbilling/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,185 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/playbilling/src/main/res/drawable/ic_launcher_foreground.xml b/playbilling/src/main/res/drawable/ic_launcher_foreground.xml
new file mode 100644
index 000000000..9e14c8458
--- /dev/null
+++ b/playbilling/src/main/res/drawable/ic_launcher_foreground.xml
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/playbilling/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/playbilling/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 000000000..d72381561
--- /dev/null
+++ b/playbilling/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
diff --git a/playbilling/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/playbilling/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 000000000..d72381561
--- /dev/null
+++ b/playbilling/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
diff --git a/playbilling/src/main/res/mipmap-hdpi/ic_launcher.webp b/playbilling/src/main/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 000000000..c209e78ec
Binary files /dev/null and b/playbilling/src/main/res/mipmap-hdpi/ic_launcher.webp differ
diff --git a/playbilling/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/playbilling/src/main/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 000000000..b2dfe3d1b
Binary files /dev/null and b/playbilling/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ
diff --git a/playbilling/src/main/res/mipmap-mdpi/ic_launcher.webp b/playbilling/src/main/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 000000000..4f0f1d64e
Binary files /dev/null and b/playbilling/src/main/res/mipmap-mdpi/ic_launcher.webp differ
diff --git a/playbilling/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/playbilling/src/main/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 000000000..62b611da0
Binary files /dev/null and b/playbilling/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ
diff --git a/playbilling/src/main/res/mipmap-xhdpi/ic_launcher.webp b/playbilling/src/main/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 000000000..948a3070f
Binary files /dev/null and b/playbilling/src/main/res/mipmap-xhdpi/ic_launcher.webp differ
diff --git a/playbilling/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/playbilling/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
new file mode 100644
index 000000000..1b9a6956b
Binary files /dev/null and b/playbilling/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ
diff --git a/playbilling/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/playbilling/src/main/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 000000000..28d4b77f9
Binary files /dev/null and b/playbilling/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ
diff --git a/playbilling/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/playbilling/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
new file mode 100644
index 000000000..9287f5083
Binary files /dev/null and b/playbilling/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ
diff --git a/playbilling/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/playbilling/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 000000000..aa7d6427e
Binary files /dev/null and b/playbilling/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ
diff --git a/playbilling/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/playbilling/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
new file mode 100644
index 000000000..9126ae37c
Binary files /dev/null and b/playbilling/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ
diff --git a/playbilling/src/main/res/values-night/themes.xml b/playbilling/src/main/res/values-night/themes.xml
new file mode 100644
index 000000000..ef64b4724
--- /dev/null
+++ b/playbilling/src/main/res/values-night/themes.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
diff --git a/playbilling/src/main/res/values/colors.xml b/playbilling/src/main/res/values/colors.xml
new file mode 100644
index 000000000..068b7a9e6
--- /dev/null
+++ b/playbilling/src/main/res/values/colors.xml
@@ -0,0 +1,25 @@
+
+
+
+ #FFBB86FC
+ #FF6200EE
+ #FF3700B3
+ #FF03DAC5
+ #FF018786
+ #FF000000
+ #FFFFFFFF
+
diff --git a/playbilling/src/main/res/values/strings.xml b/playbilling/src/main/res/values/strings.xml
new file mode 100644
index 000000000..004019a48
--- /dev/null
+++ b/playbilling/src/main/res/values/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ playbillinglibrary
+
diff --git a/playbilling/src/main/res/values/themes.xml b/playbilling/src/main/res/values/themes.xml
new file mode 100644
index 000000000..e3311ad4b
--- /dev/null
+++ b/playbilling/src/main/res/values/themes.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
diff --git a/playbilling/src/test/java/com/example/pbl/ExampleUnitTest.java b/playbilling/src/test/java/com/example/pbl/ExampleUnitTest.java
new file mode 100644
index 000000000..b267ed242
--- /dev/null
+++ b/playbilling/src/test/java/com/example/pbl/ExampleUnitTest.java
@@ -0,0 +1,17 @@
+package com.example.pbl;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see Testing documentation
+ */
+public class ExampleUnitTest {
+ @Test
+ public void addition_isCorrect() {
+ assertEquals(4, 2 + 2);
+ }
+}
\ No newline at end of file
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 7474e22fd..5df954c5b 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -45,3 +45,4 @@ include(
":kmp:androidApp",
":kmp:shared"
)
+include(":playbilling")