diff --git a/kotlin/advanced/JetpackComposeDemo/app/build.gradle.kts b/kotlin/advanced/JetpackComposeDemo/app/build.gradle.kts
new file mode 100644
index 000000000..7c699d4b4
--- /dev/null
+++ b/kotlin/advanced/JetpackComposeDemo/app/build.gradle.kts
@@ -0,0 +1,59 @@
+plugins {
+ id("com.android.application")
+ id("org.jetbrains.kotlin.android")
+}
+
+android {
+ namespace = "com.example.jetpackcomposedemo"
+ compileSdk = 34
+
+ defaultConfig {
+ applicationId = "com.google.android.gms.example.jetpackcomposedemo"
+ minSdk = 24
+ targetSdk = 34
+ versionCode = 1
+ versionName = "1.0"
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ vectorDrawables { useSupportLibrary = true }
+ }
+
+ buildTypes {
+ release {
+ isMinifyEnabled = false
+ proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
+ }
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+ }
+ kotlinOptions { jvmTarget = "1.8" }
+ buildFeatures {
+ compose = true
+ viewBinding = true
+ }
+ composeOptions { kotlinCompilerExtensionVersion = "1.5.1" }
+ packaging { resources { excludes += "/META-INF/{AL2.0,LGPL2.1}" } }
+}
+
+dependencies {
+ implementation("androidx.core:core-ktx:1.13.1")
+ implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.0")
+ implementation("androidx.activity:activity-compose:1.9.0")
+ implementation(platform("androidx.compose:compose-bom:2024.05.00"))
+ implementation("androidx.compose.ui:ui")
+ implementation("androidx.compose.ui:ui-graphics")
+ implementation("androidx.compose.ui:ui-tooling-preview")
+ implementation("androidx.compose.material3:material3")
+ implementation("com.google.android.gms:play-services-ads:23.1.0")
+ implementation("com.google.android.ump:user-messaging-platform:2.2.0")
+ implementation("com.google.android.gms:play-services-ads-lite:23.1.0")
+ testImplementation("junit:junit:4.13.2")
+ androidTestImplementation("androidx.test.ext:junit:1.1.5")
+ androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
+ androidTestImplementation(platform("androidx.compose:compose-bom:2024.05.00"))
+ androidTestImplementation("androidx.compose.ui:ui-test-junit4")
+ debugImplementation("androidx.compose.ui:ui-tooling")
+ debugImplementation("androidx.compose.ui:ui-test-manifest")
+}
diff --git a/kotlin/advanced/JetpackComposeDemo/app/proguard-rules.pro b/kotlin/advanced/JetpackComposeDemo/app/proguard-rules.pro
new file mode 100644
index 000000000..f1b424510
--- /dev/null
+++ b/kotlin/advanced/JetpackComposeDemo/app/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
diff --git a/kotlin/advanced/JetpackComposeDemo/app/src/main/AndroidManifest.xml b/kotlin/advanced/JetpackComposeDemo/app/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..fd78fadba
--- /dev/null
+++ b/kotlin/advanced/JetpackComposeDemo/app/src/main/AndroidManifest.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/kotlin/advanced/JetpackComposeDemo/app/src/main/java/com/google/android/gms/example/jetpackcomposedemo/BannerActivity.kt b/kotlin/advanced/JetpackComposeDemo/app/src/main/java/com/google/android/gms/example/jetpackcomposedemo/BannerActivity.kt
new file mode 100644
index 000000000..01ac783b0
--- /dev/null
+++ b/kotlin/advanced/JetpackComposeDemo/app/src/main/java/com/google/android/gms/example/jetpackcomposedemo/BannerActivity.kt
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2024 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
+ *
+ * http://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.google.android.gms.example.jetpackcomposedemo
+
+import android.content.Intent
+import android.os.Bundle
+import android.util.Log
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.filled.ArrowBack
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBar
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.tooling.preview.Preview
+import com.google.android.gms.ads.AdRequest
+import com.google.android.gms.ads.AdSize
+import com.google.android.gms.ads.LoadAdError
+import com.google.android.gms.example.jetpackcomposedemo.composables.BannerAd
+import com.google.android.gms.example.jetpackcomposedemo.composables.BannerAdState
+import com.google.android.gms.example.jetpackcomposedemo.ui.theme.ColorStateError
+import com.google.android.gms.example.jetpackcomposedemo.ui.theme.ColorStateLoaded
+import com.google.android.gms.example.jetpackcomposedemo.ui.theme.ColorStateUnloaded
+import com.google.android.gms.example.jetpackcomposedemo.ui.theme.JetpackComposeDemoTheme
+
+class BannerActivity : ComponentActivity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContent {
+ JetpackComposeDemoTheme {
+ Surface(modifier = Modifier.fillMaxHeight(), color = MaterialTheme.colorScheme.background) {
+ BannerScreen()
+ }
+ }
+ }
+ }
+
+ @Preview
+ @Composable
+ fun BannerScreenPreview() {
+ JetpackComposeDemoTheme {
+ Surface(modifier = Modifier.fillMaxHeight(), color = MaterialTheme.colorScheme.background) {
+ BannerScreen()
+ }
+ }
+ }
+
+ @OptIn(ExperimentalMaterial3Api::class)
+ @Composable
+ fun BannerScreen() {
+ // Cache the mutable state for our notification bar.
+ val context = LocalContext.current
+ var messageText by remember { mutableStateOf("Banner ad is not loaded.") }
+ var messageColor by remember { mutableStateOf(ColorStateUnloaded) }
+
+ // Construct a banner ad state which configures our BannerAd composable.
+ val bannerState =
+ BannerAdState(
+ adUnitId = ADUNIT_ID,
+ adSize = AdSize.BANNER,
+ adRequest = AdRequest.Builder().build(),
+ onAdLoaded = {
+ messageColor = ColorStateLoaded
+ messageText = "Banner ad is loaded."
+ Log.i(TAG, messageText)
+ },
+ onAdFailedToLoad = { error: LoadAdError ->
+ messageColor = ColorStateError
+ messageText = "Banner ad failed to load with error: ${error.message}"
+ Log.e(TAG, messageText)
+ },
+ onAdImpression = { Log.i(TAG, "Banner ad impression.") },
+ onAdClicked = { Log.i(TAG, "Banner ad clicked.") },
+ onAdOpened = { Log.i(TAG, "Banner ad opened.") },
+ onAdClosed = { Log.i(TAG, "Banner ad closed.") },
+ )
+
+ Column(
+ modifier = Modifier.verticalScroll(rememberScrollState()),
+ content = {
+ // Render title.
+ TopAppBar(
+ title = { Text(text = "Banner ad") },
+ navigationIcon = {
+ IconButton(
+ onClick = {
+ val intent = Intent(context, MainActivity::class.java)
+ context.startActivity(intent)
+ }
+ ) {
+ Icon(Icons.AutoMirrored.Filled.ArrowBack, "Back")
+ }
+ },
+ )
+ // Render status.
+ Box(modifier = Modifier.fillMaxSize().background(messageColor)) {
+ Text(text = messageText, style = MaterialTheme.typography.bodyLarge)
+ }
+ // Render the BannerAd composable.
+ BannerAd(bannerState, modifier = Modifier)
+ },
+ )
+ }
+
+ companion object {
+ const val TAG = "GoogleMobileAdsSample"
+ // Test AdUnitID for demonstrative purposes.
+ // https://developers.google.com/admob/android/test-ads
+ const val ADUNIT_ID = "ca-app-pub-3940256099942544/6300978111"
+ }
+}
diff --git a/kotlin/advanced/JetpackComposeDemo/app/src/main/java/com/google/android/gms/example/jetpackcomposedemo/GoogleMobileAdsManager.kt b/kotlin/advanced/JetpackComposeDemo/app/src/main/java/com/google/android/gms/example/jetpackcomposedemo/GoogleMobileAdsManager.kt
new file mode 100644
index 000000000..101c8fed5
--- /dev/null
+++ b/kotlin/advanced/JetpackComposeDemo/app/src/main/java/com/google/android/gms/example/jetpackcomposedemo/GoogleMobileAdsManager.kt
@@ -0,0 +1,153 @@
+package com.google.android.gms.example.jetpackcomposedemo
+
+import android.app.Activity
+import android.content.Context
+import android.util.Log
+import androidx.annotation.IntDef
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.mutableStateOf
+import com.google.android.gms.ads.AdError
+import com.google.android.gms.ads.MobileAds
+import com.google.android.ump.*
+import java.util.concurrent.atomic.AtomicBoolean
+
+/** This class manages the process of obtaining consent for and initializing Google Mobile Ads. */
+class GoogleMobileAdsManager {
+
+ /** Represents initialization states for the Google Mobile Ads SDK. */
+ object MobileAdsState {
+ /** Initial start state. */
+ const val UNINITIALIZED = 0
+
+ /** User consent required but not yet obtained. */
+ const val CONSENT_REQUIRED = 2
+
+ /** User consent obtained. Personalized vs non-personalized undefined. */
+ const val CONSENT_OBTAINED = 3
+
+ /** Google Mobile Ads SDK initialized successfully. */
+ const val INITIALIZED = 100
+
+ /** An error occurred when requesting consent. */
+ const val CONSENT_REQUEST_ERROR = -1
+
+ /** An error occurred when showing the privacy options form. */
+ const val CONSENT_FORM_ERROR = -2
+
+ @Target(AnnotationTarget.TYPE)
+ @IntDef(
+ UNINITIALIZED,
+ INITIALIZED,
+ CONSENT_REQUIRED,
+ CONSENT_OBTAINED,
+ CONSENT_REQUEST_ERROR,
+ CONSENT_FORM_ERROR,
+ )
+ @Retention(AnnotationRetention.SOURCE)
+ annotation class State
+ }
+
+ /** Represents current initialization states for the Google Mobile Ads SDK. */
+ var mobileAdsState = mutableIntStateOf(MobileAdsState.UNINITIALIZED)
+
+ /** Indicates whether the app has completed the steps for gathering updated user consent. */
+ var canRequestAds = mutableStateOf(false)
+
+ /** Helper variable to determine if the privacy options form is required. */
+ var isPrivacyOptionsRequired = mutableStateOf(false)
+
+ private var isMobileAdsInitializeCalled = AtomicBoolean(false)
+
+ private lateinit var consentInformation: ConsentInformation
+
+ /**
+ * Initiates the consent process and initializes the Google Mobile Ads SDK if the SDK is
+ * UNINITIALIZED.
+ *
+ * @param activity Activity responsible for initializing the Google Mobile Ads SDK.
+ * @param consentRequestParameters Parameters for the consent request form.
+ */
+ fun initializeWithConsent(
+ activity: Activity,
+ consentRequestParameters: ConsentRequestParameters,
+ ) {
+
+ if (isMobileAdsInitializeCalled.getAndSet(true)) {
+ return
+ }
+
+ consentInformation = UserMessagingPlatform.getConsentInformation(activity)
+
+ consentInformation.requestConsentInfoUpdate(
+ activity,
+ consentRequestParameters,
+ {
+ // Success callback.
+ showConsentFormIfRequired(activity) { error ->
+ if (error != null) {
+ Log.w(TAG, "Consent form error: ${error.errorCode} - ${error.message}")
+ mobileAdsState.intValue = MobileAdsState.CONSENT_FORM_ERROR
+ } else {
+ mobileAdsState.intValue = MobileAdsState.CONSENT_OBTAINED
+ }
+ canRequestAds.value = consentInformation.canRequestAds()
+ isPrivacyOptionsRequired.value =
+ consentInformation.privacyOptionsRequirementStatus ==
+ ConsentInformation.PrivacyOptionsRequirementStatus.REQUIRED
+ if (consentInformation.canRequestAds()) {
+ initializeMobileAdsSdk(activity)
+ }
+ }
+ },
+ { formError ->
+ // Failure callback.
+ Log.w(TAG, "Consent info update error: ${formError.errorCode} - ${formError.message}")
+ mobileAdsState.intValue = MobileAdsState.CONSENT_REQUEST_ERROR
+ },
+ )
+
+ // This sample attempts to load ads using consent obtained from the previous session.
+ if (consentInformation.canRequestAds()) {
+ initializeMobileAdsSdk(activity)
+ }
+ }
+
+ /** Shows the update consent form. */
+ fun showPrivacyOptionsForm(activity: Activity) {
+ UserMessagingPlatform.showPrivacyOptionsForm(activity) { error ->
+ if (error != null) {
+ mobileAdsState.intValue = MobileAdsState.CONSENT_FORM_ERROR
+ }
+ }
+ }
+
+ /**
+ * Initializes Mobile Ads SDK.
+ *
+ * @param activity Activity responsible for initializing the Google Mobile Ads SDK.
+ */
+ fun initializeMobileAdsSdk(activity: Activity) {
+ MobileAds.initialize(activity) {
+ Log.d(TAG, "Mobile Ads SDK initialized")
+ mobileAdsState.intValue = MobileAdsState.INITIALIZED
+ }
+ }
+
+ /**
+ * Opens the Ad Inspector UI.
+ *
+ * @param context The application or activity context required for launching the inspector.
+ * @param onAdInspectorResult A callback to handle the result of opening the Ad Inspector.
+ */
+ fun openAdInspector(context: Context, onAdInspectorResult: (AdError?) -> Unit) {
+ MobileAds.openAdInspector(context) { error -> onAdInspectorResult(error) }
+ }
+
+ private fun showConsentFormIfRequired(activity: Activity, onFormResult: (FormError?) -> Unit) {
+ UserMessagingPlatform.loadAndShowConsentFormIfRequired(activity, onFormResult)
+ }
+
+ companion object {
+ const val TAG = "GoogleMobileAdsSample"
+ }
+}
diff --git a/kotlin/advanced/JetpackComposeDemo/app/src/main/java/com/google/android/gms/example/jetpackcomposedemo/MainActivity.kt b/kotlin/advanced/JetpackComposeDemo/app/src/main/java/com/google/android/gms/example/jetpackcomposedemo/MainActivity.kt
new file mode 100644
index 000000000..1e21cfba0
--- /dev/null
+++ b/kotlin/advanced/JetpackComposeDemo/app/src/main/java/com/google/android/gms/example/jetpackcomposedemo/MainActivity.kt
@@ -0,0 +1,195 @@
+/*
+ * Copyright 2024 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
+ *
+ * http://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.google.android.gms.example.jetpackcomposedemo
+
+import android.content.Intent
+import android.os.Bundle
+import android.util.Log
+import android.widget.Toast
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBar
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.tooling.preview.Preview
+import com.example.jetpackcomposedemo.R
+import com.google.android.gms.ads.MobileAds
+import com.google.android.gms.ads.RequestConfiguration
+import com.google.android.gms.example.jetpackcomposedemo.ui.TextButton
+import com.google.android.gms.example.jetpackcomposedemo.ui.theme.ColorStateError
+import com.google.android.gms.example.jetpackcomposedemo.ui.theme.ColorStateLoaded
+import com.google.android.gms.example.jetpackcomposedemo.ui.theme.ColorStateUnloaded
+import com.google.android.gms.example.jetpackcomposedemo.ui.theme.JetpackComposeDemoTheme
+import com.google.android.ump.ConsentDebugSettings
+import com.google.android.ump.ConsentRequestParameters
+
+class MainActivity : ComponentActivity() {
+
+ // This instance manages the process initializing Google Mobile Ads.
+ private val adsManager = GoogleMobileAdsManager()
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ // Initialize the Google Mobile Ads SDK.
+ if (adsManager.mobileAdsState.intValue != GoogleMobileAdsManager.MobileAdsState.INITIALIZED) {
+ initMobileAds()
+ }
+
+ setContent {
+ JetpackComposeDemoTheme {
+ Surface(modifier = Modifier.fillMaxHeight(), color = MaterialTheme.colorScheme.background) {
+ MainScreen()
+ }
+ }
+ }
+ }
+
+ private fun initMobileAds() {
+ // Always use test ads: https://developers.google.com/admob/android/test-ads#kotlin
+ val testDeviceIds = listOf("33BE2250B43518CCDA7DE426D04EE231")
+
+ // Configure RequestConfiguration.
+ val configuration = RequestConfiguration.Builder().setTestDeviceIds(testDeviceIds).build()
+ MobileAds.setRequestConfiguration(configuration)
+
+ val debugSettings = ConsentDebugSettings.Builder(this)
+ // For testing purposes, you can force a DebugGeography of EEA or NOT_EEA.
+ // debugSettings.setDebugGeography(ConsentDebugSettings.DebugGeography.DEBUG_GEOGRAPHY_EEA);
+
+ testDeviceIds.forEach { deviceId -> debugSettings.addTestDeviceHashedId(deviceId) }
+ val consentRequestParameters =
+ ConsentRequestParameters.Builder().setConsentDebugSettings(debugSettings.build()).build()
+
+ adsManager.initializeWithConsent(this, consentRequestParameters)
+ }
+
+ @Composable
+ @Preview
+ fun MainScreenPreview() {
+ JetpackComposeDemoTheme {
+ Surface(modifier = Modifier.fillMaxHeight(), color = MaterialTheme.colorScheme.background) {
+ MainScreen()
+ }
+ }
+ }
+
+ @OptIn(ExperimentalMaterial3Api::class)
+ @Composable
+ fun MainScreen() {
+ val context = LocalContext.current
+ val activity = this
+
+ Column(
+ modifier = Modifier.verticalScroll(rememberScrollState()),
+ content = {
+ // Render title.
+ TopAppBar(title = { Text(resources.getString(R.string.main_title)) })
+ // Render mobile ads status.
+ Box(
+ modifier =
+ Modifier.fillMaxSize().background(adsManager.mobileAdsState.intValue.messageColor())
+ ) {
+ Text(
+ text = adsManager.mobileAdsState.intValue.messageText(),
+ style = MaterialTheme.typography.bodyLarge,
+ )
+ }
+ // Show Consent Form.
+ TextButton(
+ name = resources.getString(R.string.consent_show),
+ enabled = adsManager.isPrivacyOptionsRequired.value,
+ ) {
+ adsManager.showPrivacyOptionsForm(activity)
+ }
+ // Open Ad Inspector.
+ TextButton(name = resources.getString(R.string.adinspector)) {
+ adsManager.openAdInspector(context) { error ->
+ if (error != null) {
+ Toast.makeText(
+ context,
+ resources.getString(R.string.adinspector_error),
+ Toast.LENGTH_LONG,
+ )
+ .show()
+ Log.e(
+ TAG,
+ String.format(resources.getString(R.string.adinspector_error), error.message),
+ )
+ }
+ }
+ }
+ // Banner Sample.
+ TextButton(name = "Banner", enabled = adsManager.canRequestAds.value) {
+ val intent = Intent(context, BannerActivity::class.java)
+ context.startActivity(intent)
+ }
+ // Native Sample.
+ TextButton(name = "Native", enabled = adsManager.canRequestAds.value) {
+ val intent = Intent(context, NativeActivity::class.java)
+ context.startActivity(intent)
+ }
+ },
+ )
+ }
+
+ // Extend MobileAdsState with message color.
+ private fun @GoogleMobileAdsManager.MobileAdsState.State Int.messageColor(): Color {
+ return when (this) {
+ GoogleMobileAdsManager.MobileAdsState.CONSENT_OBTAINED -> ColorStateUnloaded
+ GoogleMobileAdsManager.MobileAdsState.CONSENT_REQUEST_ERROR -> ColorStateError
+ GoogleMobileAdsManager.MobileAdsState.CONSENT_FORM_ERROR -> ColorStateError
+ GoogleMobileAdsManager.MobileAdsState.INITIALIZED -> ColorStateLoaded
+ else -> ColorStateUnloaded
+ }
+ }
+
+ // Extend MobileAdsState with message text.
+ private fun @GoogleMobileAdsManager.MobileAdsState.State Int.messageText(): String {
+ return when (this) {
+ GoogleMobileAdsManager.MobileAdsState.UNINITIALIZED ->
+ resources.getString(R.string.mobileads_uninitialized)
+ GoogleMobileAdsManager.MobileAdsState.CONSENT_REQUIRED ->
+ resources.getString(R.string.mobileads_consentRequired)
+ GoogleMobileAdsManager.MobileAdsState.CONSENT_OBTAINED ->
+ resources.getString(R.string.mobileads_consentObtained)
+ GoogleMobileAdsManager.MobileAdsState.CONSENT_REQUEST_ERROR ->
+ resources.getString(R.string.mobileads_consentError)
+ GoogleMobileAdsManager.MobileAdsState.CONSENT_FORM_ERROR ->
+ resources.getString(R.string.mobileads_consentFormError)
+ GoogleMobileAdsManager.MobileAdsState.INITIALIZED ->
+ resources.getString(R.string.mobileads_initialized)
+ else -> resources.getString(R.string.mobileads_uninitialized)
+ }
+ }
+
+ companion object {
+ const val TAG = "GoogleMobileAdsSample"
+ }
+}
diff --git a/kotlin/advanced/JetpackComposeDemo/app/src/main/java/com/google/android/gms/example/jetpackcomposedemo/MobileAdsApplication.kt b/kotlin/advanced/JetpackComposeDemo/app/src/main/java/com/google/android/gms/example/jetpackcomposedemo/MobileAdsApplication.kt
new file mode 100644
index 000000000..639511e7c
--- /dev/null
+++ b/kotlin/advanced/JetpackComposeDemo/app/src/main/java/com/google/android/gms/example/jetpackcomposedemo/MobileAdsApplication.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2024 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
+ *
+ * http://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.google.android.gms.example.jetpackcomposedemo
+
+import android.app.Application
+
+class MobileAdsApplication : Application() {
+ companion object {
+ const val TAG = "GoogleMobileAdsSample"
+ }
+}
diff --git a/kotlin/advanced/JetpackComposeDemo/app/src/main/java/com/google/android/gms/example/jetpackcomposedemo/NativeActivity.kt b/kotlin/advanced/JetpackComposeDemo/app/src/main/java/com/google/android/gms/example/jetpackcomposedemo/NativeActivity.kt
new file mode 100644
index 000000000..c30f4059f
--- /dev/null
+++ b/kotlin/advanced/JetpackComposeDemo/app/src/main/java/com/google/android/gms/example/jetpackcomposedemo/NativeActivity.kt
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2024 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
+ *
+ * http://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.google.android.gms.example.jetpackcomposedemo
+
+import android.content.Intent
+import android.os.Bundle
+import android.util.Log
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.filled.ArrowBack
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBar
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.example.jetpackcomposedemo.R
+import com.google.android.gms.ads.AdRequest
+import com.google.android.gms.ads.LoadAdError
+import com.google.android.gms.example.jetpackcomposedemo.composables.NativeAd
+import com.google.android.gms.example.jetpackcomposedemo.composables.NativeAdState
+import com.google.android.gms.example.jetpackcomposedemo.ui.theme.ColorStateError
+import com.google.android.gms.example.jetpackcomposedemo.ui.theme.ColorStateLoaded
+import com.google.android.gms.example.jetpackcomposedemo.ui.theme.ColorStateUnloaded
+import com.google.android.gms.example.jetpackcomposedemo.ui.theme.JetpackComposeDemoTheme
+
+class NativeActivity : ComponentActivity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContent {
+ JetpackComposeDemoTheme {
+ // A surface container using the 'background' color from the theme
+ Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
+ NativeLayoutScreen()
+ }
+ }
+ }
+ }
+
+ @Preview
+ @Composable
+ fun NativeLayoutScreenPreview() {
+ JetpackComposeDemoTheme {
+ // A surface container using the 'background' color from the theme
+ Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
+ NativeLayoutScreen()
+ }
+ }
+ }
+
+ @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3Api::class)
+ @Composable
+ fun NativeLayoutScreen() {
+ // Cache the mutable state for our notification bar.
+ val context = LocalContext.current
+ var messageText by remember { mutableStateOf("Native ad is not loaded.") }
+ var messageColor by remember { mutableStateOf(ColorStateUnloaded) }
+
+ // Construct a banner state to configure the BannerComposable
+ val nativeState =
+ NativeAdState(
+ adUnitId = ADUNIT_ID,
+ adRequest = AdRequest.Builder().build(),
+ onAdLoaded = {
+ messageColor = ColorStateLoaded
+ messageText = "Native ad is loaded."
+ Log.i(TAG, messageText)
+ },
+ onAdFailedToLoad = { error: LoadAdError ->
+ messageColor = ColorStateError
+ messageText = "Native ad failed to load with error: ${error.message}"
+ Log.e(TAG, messageText)
+ },
+ onAdImpression = { Log.i(TAG, "Native ad impression") },
+ onAdClicked = { Log.i(TAG, "Native ad clicked") },
+ onAdOpened = { Log.i(TAG, "Native ad opened") },
+ onAdClosed = { Log.i(TAG, "Native ad closed.") },
+ )
+ Column(
+ modifier = Modifier.verticalScroll(rememberScrollState()),
+ content = {
+ // Render title.
+ TopAppBar(
+ title = { Text(text = "Native") },
+ navigationIcon = {
+ IconButton(
+ onClick = {
+ val intent = Intent(context, MainActivity::class.java)
+ context.startActivity(intent)
+ }
+ ) {
+ Icon(Icons.AutoMirrored.Filled.ArrowBack, "Back")
+ }
+ },
+ )
+ // Render status.
+ Box(modifier = Modifier.fillMaxSize().background(messageColor)) {
+ Text(text = messageText, style = MaterialTheme.typography.bodyLarge)
+ }
+ // Render NativeAd composable.
+ Box(modifier = Modifier.fillMaxWidth().background(Color.Gray).padding(8.dp)) {
+ NativeAd(
+ nativeAdState = nativeState,
+ R.layout.nativead,
+ modifier = Modifier.fillMaxWidth(),
+ )
+ }
+ },
+ )
+ }
+
+ companion object {
+ const val TAG = "GoogleMobileAdsSample"
+ // Test AdUnitID for demonstrative purposes.
+ // https://developers.google.com/admob/android/test-ads
+ const val ADUNIT_ID = "ca-app-pub-3940256099942544/2247696110"
+ }
+}
diff --git a/kotlin/advanced/JetpackComposeDemo/app/src/main/java/com/google/android/gms/example/jetpackcomposedemo/composables/BannerAd.kt b/kotlin/advanced/JetpackComposeDemo/app/src/main/java/com/google/android/gms/example/jetpackcomposedemo/composables/BannerAd.kt
new file mode 100644
index 000000000..46855f176
--- /dev/null
+++ b/kotlin/advanced/JetpackComposeDemo/app/src/main/java/com/google/android/gms/example/jetpackcomposedemo/composables/BannerAd.kt
@@ -0,0 +1,111 @@
+package com.google.android.gms.example.jetpackcomposedemo.composables
+
+/*
+ * Copyright 2024 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
+ *
+ * http://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.
+ */
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.width
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+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.platform.LocalInspectionMode
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.viewinterop.AndroidView
+import com.google.android.gms.ads.AdListener
+import com.google.android.gms.ads.AdView
+import com.google.android.gms.ads.LoadAdError
+
+/**
+ * A composable function to display a banner advertisement.
+ *
+ * @param bannerAdState The BannerState object containing ad configuration.
+ * @param modifier The modifier to apply to the banner ad.
+ */
+@Composable
+fun BannerAd(bannerAdState: BannerAdState, modifier: Modifier) {
+ // Remember the adView so we can dispose of it later.
+ var adView by remember { mutableStateOf(null) }
+
+ if (LocalInspectionMode.current) {
+ Box(
+ modifier =
+ Modifier.background(Color.Gray)
+ .width(bannerAdState.adSize.width.dp)
+ .height(bannerAdState.adSize.height.dp)
+ ) {
+ Text(text = "Google Mobile Ads preview banner.", modifier.align(Alignment.Center))
+ }
+ return
+ }
+
+ AndroidView(
+ modifier = modifier.fillMaxWidth(),
+ factory = { context ->
+ AdView(context).apply {
+ // Make sure we only run this code block once and in non-preview mode.
+ if (adView != null) {
+ return@apply
+ }
+
+ adView = this
+ this.adUnitId = bannerAdState.adUnitId
+ this.setAdSize(bannerAdState.adSize)
+ this.adListener =
+ object : AdListener() {
+ override fun onAdLoaded() {
+ bannerAdState.onAdLoaded?.invoke()
+ }
+
+ override fun onAdFailedToLoad(error: LoadAdError) {
+ bannerAdState.onAdFailedToLoad?.invoke(error)
+ }
+
+ override fun onAdImpression() {
+ bannerAdState.onAdImpression?.invoke()
+ }
+
+ override fun onAdClosed() {
+ bannerAdState.onAdClosed?.invoke()
+ }
+
+ override fun onAdClicked() {
+ bannerAdState.onAdClicked?.invoke()
+ }
+
+ override fun onAdOpened() {
+ bannerAdState.onAdClicked?.invoke()
+ }
+
+ override fun onAdSwipeGestureClicked() {
+ bannerAdState.onAdSwipeGestureClicked?.invoke()
+ }
+ }
+ this.loadAd(bannerAdState.adRequest)
+ }
+ },
+ )
+ // Clean up the AdView after use.
+ DisposableEffect(Unit) { onDispose { adView?.destroy() } }
+}
diff --git a/kotlin/advanced/JetpackComposeDemo/app/src/main/java/com/google/android/gms/example/jetpackcomposedemo/composables/BannerAdState.kt b/kotlin/advanced/JetpackComposeDemo/app/src/main/java/com/google/android/gms/example/jetpackcomposedemo/composables/BannerAdState.kt
new file mode 100644
index 000000000..4bfe05d66
--- /dev/null
+++ b/kotlin/advanced/JetpackComposeDemo/app/src/main/java/com/google/android/gms/example/jetpackcomposedemo/composables/BannerAdState.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2024 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
+ *
+ * http://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.google.android.gms.example.jetpackcomposedemo.composables
+
+import com.google.android.gms.ads.AdRequest
+import com.google.android.gms.ads.AdSize
+import com.google.android.gms.ads.LoadAdError
+
+/**
+ * Represents the configuration of a banner advertisement.
+ *
+ * @param adUnitId The ID of the ad unit to load the banner into.
+ * @param adRequest The AdRequest object used to configure ad targeting and loading behavior.
+ * @param adSize The desired size of the banner ad (default is AdSize.BANNER).
+ * @param onAdClicked Function invoked when the ad is clicked.
+ * @param onAdImpression Function invoked when an ad impression is recorded.
+ * @param onAdFailedToLoad Function invoked when the ad fails to load, includes the LoadAdError.
+ * @param onAdLoaded Function invoked when the ad is successfully loaded.
+ * @param onAdOpened Function invoked when the ad is opened (e.g., expands to a fullscreen).
+ * @param onAdClosed Function invoked when the ad is closed.
+ * @param onAdSwipeGestureClicked Function invoked when user performs a swipe gesture on the ad.
+ */
+data class BannerAdState(
+ val adUnitId: String,
+ val adRequest: AdRequest,
+ val adSize: AdSize = AdSize.BANNER,
+ val onAdClicked: (() -> Unit)? = null,
+ val onAdImpression: (() -> Unit)? = null,
+ val onAdFailedToLoad: ((LoadAdError) -> Unit)? = null,
+ val onAdLoaded: (() -> Unit)? = null,
+ val onAdOpened: (() -> Unit)? = null,
+ val onAdClosed: (() -> Unit)? = null,
+ val onAdSwipeGestureClicked: (() -> Unit)? = null,
+)
diff --git a/kotlin/advanced/JetpackComposeDemo/app/src/main/java/com/google/android/gms/example/jetpackcomposedemo/composables/NativeAd.kt b/kotlin/advanced/JetpackComposeDemo/app/src/main/java/com/google/android/gms/example/jetpackcomposedemo/composables/NativeAd.kt
new file mode 100644
index 000000000..458de7c54
--- /dev/null
+++ b/kotlin/advanced/JetpackComposeDemo/app/src/main/java/com/google/android/gms/example/jetpackcomposedemo/composables/NativeAd.kt
@@ -0,0 +1,164 @@
+/*
+ * Copyright 2024 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
+ *
+ * http://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.google.android.gms.example.jetpackcomposedemo.composables
+
+import android.view.LayoutInflater
+import android.view.View
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalInspectionMode
+import androidx.compose.ui.viewinterop.AndroidView
+import com.example.jetpackcomposedemo.databinding.NativeadBinding
+import com.google.android.gms.ads.AdListener
+import com.google.android.gms.ads.AdLoader
+import com.google.android.gms.ads.LoadAdError
+import com.google.android.gms.ads.nativead.NativeAd
+import com.google.android.gms.ads.nativead.NativeAdView
+
+/**
+ * A composable function to display a native ad with a native ad view layout defined in xml.
+ *
+ * @param nativeAdState The NativeAdState object containing ad configuration.
+ * @param layoutID The layout resource Id to use for the native ad view.
+ * @param modifier The modifier to apply to the banner ad.
+ */
+@Composable
+fun NativeAd(nativeAdState: NativeAdState, layoutID: Int, modifier: Modifier) {
+ // Do not load the ad in preview mode
+ if (LocalInspectionMode.current) {
+ Box(modifier = Modifier.background(Color.Gray)) {
+ Text(text = "Native Ad Preview.", modifier.align(Alignment.Center))
+ }
+ return
+ }
+
+ var currentNativeAdView: NativeAdView? = null
+ var currentNativeAd: NativeAd? = null
+
+ val adLoader = AdLoader.Builder(LocalContext.current, nativeAdState.adUnitId)
+ if (nativeAdState.nativeAdOptions != null) {
+ adLoader.withNativeAdOptions(nativeAdState.nativeAdOptions)
+ }
+ adLoader.withAdListener(
+ object : AdListener() {
+ override fun onAdFailedToLoad(error: LoadAdError) {
+ nativeAdState.onAdFailedToLoad?.invoke(error)
+ }
+
+ override fun onAdLoaded() {
+ nativeAdState.onAdLoaded?.invoke()
+ }
+
+ override fun onAdClicked() {
+ nativeAdState.onAdClicked?.invoke()
+ }
+
+ override fun onAdClosed() {
+ nativeAdState.onAdClosed?.invoke()
+ }
+
+ override fun onAdImpression() {
+ nativeAdState.onAdImpression?.invoke()
+ }
+
+ override fun onAdOpened() {
+ nativeAdState.onAdOpened?.invoke()
+ }
+
+ override fun onAdSwipeGestureClicked() {
+ nativeAdState.onAdSwipeGestureClicked?.invoke()
+ }
+ }
+ )
+ adLoader.forNativeAd { nativeAd ->
+
+ // Destroy old native ad assets to prevent memory leaks.
+ currentNativeAd?.destroy()
+ currentNativeAd = null
+ currentNativeAd = nativeAd
+
+ if (currentNativeAdView == null) {
+ return@forNativeAd
+ }
+
+ // Bind our native ad view with the native ad assets.
+ // This file is generated from /res/layouts/nativead
+ currentNativeAdView?.let { adView ->
+ val binding = NativeadBinding.bind(adView)
+ binding.adHeadline.text = nativeAd.headline
+ binding.adBody.text = nativeAd.body
+ binding.adCallToAction.text = nativeAd.callToAction
+ binding.adPrice.text = nativeAd.price
+ binding.adStore.text = nativeAd.store
+ binding.adStars.rating = nativeAd.starRating?.toFloat() ?: 0.toFloat()
+ binding.adAdvertiser.text = nativeAd.advertiser
+ binding.adAppIcon.setImageDrawable(nativeAd.icon?.drawable)
+
+ // Hide unused native ad view elements.
+ binding.adBody.visibility = nativeAd.body?.let { View.VISIBLE } ?: View.GONE
+ binding.adCallToAction.visibility = nativeAd.callToAction?.let { View.VISIBLE } ?: View.GONE
+ binding.adPrice.visibility = nativeAd.price?.let { View.VISIBLE } ?: View.GONE
+ binding.adStore.visibility = nativeAd.store?.let { View.VISIBLE } ?: View.GONE
+ binding.adStars.visibility = nativeAd.starRating?.let { View.VISIBLE } ?: View.GONE
+ binding.adAdvertiser.visibility = nativeAd.advertiser?.let { View.VISIBLE } ?: View.GONE
+ binding.adAppIcon.visibility = nativeAd.icon?.let { View.VISIBLE } ?: View.GONE
+ binding.adMedia.visibility = nativeAd.mediaContent?.let { View.VISIBLE } ?: View.GONE
+
+ // Set the mediaView just before calling setNativeAd.
+ adView.mediaView = binding.adMedia
+
+ // This method tells the Google Mobile Ads SDK that you have finished populating your
+ // native ad view with this native ad.
+ adView.setNativeAd(nativeAd)
+
+ // TODO: Remove after androidx.compose.ui:ui:1.7.0-beta04
+ adView.viewTreeObserver?.dispatchOnGlobalLayout()
+ }
+ }
+
+ AndroidView(
+ modifier = modifier,
+ factory = { context ->
+ LayoutInflater.from(context).inflate(layoutID, null, false) as NativeAdView
+ },
+ ) { nativeAdView ->
+ currentNativeAdView = nativeAdView
+ return@AndroidView
+ }
+
+ LaunchedEffect(Unit) {
+ // Load the native ad.
+ adLoader.build().loadAd(nativeAdState.adRequest)
+ }
+
+ // Clean up the native ad view after use.
+ DisposableEffect(Unit) {
+ onDispose {
+ // Destroy old native ad assets to prevent memory leaks.
+ currentNativeAd?.destroy()
+ currentNativeAd = null
+ }
+ }
+}
diff --git a/kotlin/advanced/JetpackComposeDemo/app/src/main/java/com/google/android/gms/example/jetpackcomposedemo/composables/NativeAdState.kt b/kotlin/advanced/JetpackComposeDemo/app/src/main/java/com/google/android/gms/example/jetpackcomposedemo/composables/NativeAdState.kt
new file mode 100644
index 000000000..b31d49452
--- /dev/null
+++ b/kotlin/advanced/JetpackComposeDemo/app/src/main/java/com/google/android/gms/example/jetpackcomposedemo/composables/NativeAdState.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2024 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
+ *
+ * http://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.google.android.gms.example.jetpackcomposedemo.composables
+
+import com.google.android.gms.ads.AdRequest
+import com.google.android.gms.ads.LoadAdError
+import com.google.android.gms.ads.nativead.NativeAdOptions
+
+/**
+ * Represents the state of a banner advertisement, including its configuration.
+ *
+ * @param adUnitId The ID of the ad unit to load the banner into.
+ * @param adRequest The AdRequest object used to configure ad targeting and loading behavior.
+ * @param nativeAdOptions The native ad options used to configure ad behavior.
+ * @param onAdClicked Function invoked when the ad is clicked.
+ * @param onAdImpression Function invoked when an ad impression is recorded.
+ * @param onAdFailedToLoad Function invoked when the ad fails to load, includes the LoadAdError.
+ * @param onAdLoaded Function invoked when the ad is successfully loaded.
+ * @param onAdOpened Function invoked when the ad is opened (e.g., expands to a fullscreen).
+ * @param onAdClosed Function invoked when the ad is closed.
+ * @param onAdSwipeGestureClicked Function invoked when user performs a swipe gesture on the ad.
+ */
+data class NativeAdState(
+ val adUnitId: String,
+ val adRequest: AdRequest,
+ val nativeAdOptions: NativeAdOptions? = null,
+ val onAdClicked: (() -> Unit)? = null,
+ val onAdImpression: (() -> Unit)? = null,
+ val onAdFailedToLoad: ((LoadAdError) -> Unit)? = null,
+ val onAdLoaded: (() -> Unit)? = null,
+ val onAdOpened: (() -> Unit)? = null,
+ val onAdClosed: (() -> Unit)? = null,
+ val onAdSwipeGestureClicked: (() -> Unit)? = null,
+)
diff --git a/kotlin/advanced/JetpackComposeDemo/app/src/main/java/com/google/android/gms/example/jetpackcomposedemo/ui/TextButton.kt b/kotlin/advanced/JetpackComposeDemo/app/src/main/java/com/google/android/gms/example/jetpackcomposedemo/ui/TextButton.kt
new file mode 100644
index 000000000..821359c38
--- /dev/null
+++ b/kotlin/advanced/JetpackComposeDemo/app/src/main/java/com/google/android/gms/example/jetpackcomposedemo/ui/TextButton.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2024 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
+ *
+ * http://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.google.android.gms.example.jetpackcomposedemo.ui
+
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.material3.Button
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+
+/**
+ * A composable function to create a standard button with text.
+ *
+ * @param name The text to be displayed on the button.
+ * @param enabled Controls whether the button is enabled or disabled (defaults to true).
+ * @param modifier The Modifier to be applied to this button.
+ * @param onClick The lambda function to be executed when the button is clicked.
+ */
+@Composable
+fun TextButton(
+ name: String,
+ modifier: Modifier = Modifier,
+ enabled: Boolean = true,
+ onClick: () -> Unit,
+) {
+ Button(onClick = { onClick() }, enabled = enabled, modifier = modifier.fillMaxWidth()) {
+ Text(name)
+ }
+}
diff --git a/kotlin/advanced/JetpackComposeDemo/app/src/main/java/com/google/android/gms/example/jetpackcomposedemo/ui/theme/Color.kt b/kotlin/advanced/JetpackComposeDemo/app/src/main/java/com/google/android/gms/example/jetpackcomposedemo/ui/theme/Color.kt
new file mode 100644
index 000000000..cd67390b2
--- /dev/null
+++ b/kotlin/advanced/JetpackComposeDemo/app/src/main/java/com/google/android/gms/example/jetpackcomposedemo/ui/theme/Color.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2024 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
+ *
+ * http://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.google.android.gms.example.jetpackcomposedemo.ui.theme
+
+import androidx.compose.ui.graphics.Color
+
+val Purple80 = Color(0xFFD0BCFF)
+val PurpleGrey80 = Color(0xFFCCC2DC)
+val Pink80 = Color(0xFFEFB8C8)
+
+val Purple40 = Color(0xFF6650a4)
+val PurpleGrey40 = Color(0xFF625b71)
+val Pink40 = Color(0xFF7D5260)
+
+val ColorStateLoaded = Color(0xFF009900)
+val ColorStateUnloaded = Color(0xFFcc6600)
+val ColorStateError = Color(0xFFcc0000)
diff --git a/kotlin/advanced/JetpackComposeDemo/app/src/main/java/com/google/android/gms/example/jetpackcomposedemo/ui/theme/Theme.kt b/kotlin/advanced/JetpackComposeDemo/app/src/main/java/com/google/android/gms/example/jetpackcomposedemo/ui/theme/Theme.kt
new file mode 100644
index 000000000..0b35ba825
--- /dev/null
+++ b/kotlin/advanced/JetpackComposeDemo/app/src/main/java/com/google/android/gms/example/jetpackcomposedemo/ui/theme/Theme.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2024 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
+ *
+ * http://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.google.android.gms.example.jetpackcomposedemo.ui.theme
+
+import android.app.Activity
+import android.os.Build
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.darkColorScheme
+import androidx.compose.material3.dynamicDarkColorScheme
+import androidx.compose.material3.dynamicLightColorScheme
+import androidx.compose.material3.lightColorScheme
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.SideEffect
+import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalView
+import androidx.core.view.WindowCompat
+
+private val DarkColorScheme =
+ darkColorScheme(primary = Purple80, secondary = PurpleGrey80, tertiary = Pink80)
+
+private val LightColorScheme =
+ lightColorScheme(primary = Purple40, secondary = PurpleGrey40, tertiary = Pink40)
+
+@Composable
+fun JetpackComposeDemoTheme(
+ darkTheme: Boolean = isSystemInDarkTheme(),
+ // Dynamic color is available on Android 12+.
+ dynamicColor: Boolean = true,
+ content: @Composable () -> Unit,
+) {
+ val colorScheme =
+ when {
+ dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
+ val context = LocalContext.current
+ if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
+ }
+ darkTheme -> DarkColorScheme
+ else -> LightColorScheme
+ }
+ val view = LocalView.current
+ if (!view.isInEditMode) {
+ SideEffect {
+ val window = (view.context as Activity).window
+ window.statusBarColor = colorScheme.primary.toArgb()
+ WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme
+ }
+ }
+
+ MaterialTheme(colorScheme = colorScheme, content = content)
+}
diff --git a/kotlin/advanced/JetpackComposeDemo/app/src/main/res/drawable/ic_launcher_background.xml b/kotlin/advanced/JetpackComposeDemo/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 000000000..61bb79edb
--- /dev/null
+++ b/kotlin/advanced/JetpackComposeDemo/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/kotlin/advanced/JetpackComposeDemo/app/src/main/res/drawable/ic_launcher_foreground.xml b/kotlin/advanced/JetpackComposeDemo/app/src/main/res/drawable/ic_launcher_foreground.xml
new file mode 100644
index 000000000..04d1a347b
--- /dev/null
+++ b/kotlin/advanced/JetpackComposeDemo/app/src/main/res/drawable/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/kotlin/advanced/JetpackComposeDemo/app/src/main/res/layout/nativead.xml b/kotlin/advanced/JetpackComposeDemo/app/src/main/res/layout/nativead.xml
new file mode 100644
index 000000000..c090d3886
--- /dev/null
+++ b/kotlin/advanced/JetpackComposeDemo/app/src/main/res/layout/nativead.xml
@@ -0,0 +1,141 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/kotlin/advanced/JetpackComposeDemo/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/kotlin/advanced/JetpackComposeDemo/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 000000000..3fe244199
--- /dev/null
+++ b/kotlin/advanced/JetpackComposeDemo/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/kotlin/advanced/JetpackComposeDemo/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/kotlin/advanced/JetpackComposeDemo/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 000000000..3fe244199
--- /dev/null
+++ b/kotlin/advanced/JetpackComposeDemo/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/kotlin/advanced/JetpackComposeDemo/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/kotlin/advanced/JetpackComposeDemo/app/src/main/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 000000000..c209e78ec
Binary files /dev/null and b/kotlin/advanced/JetpackComposeDemo/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ
diff --git a/kotlin/advanced/JetpackComposeDemo/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/kotlin/advanced/JetpackComposeDemo/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 000000000..b2dfe3d1b
Binary files /dev/null and b/kotlin/advanced/JetpackComposeDemo/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ
diff --git a/kotlin/advanced/JetpackComposeDemo/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/kotlin/advanced/JetpackComposeDemo/app/src/main/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 000000000..4f0f1d64e
Binary files /dev/null and b/kotlin/advanced/JetpackComposeDemo/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ
diff --git a/kotlin/advanced/JetpackComposeDemo/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/kotlin/advanced/JetpackComposeDemo/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 000000000..62b611da0
Binary files /dev/null and b/kotlin/advanced/JetpackComposeDemo/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ
diff --git a/kotlin/advanced/JetpackComposeDemo/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/kotlin/advanced/JetpackComposeDemo/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 000000000..948a3070f
Binary files /dev/null and b/kotlin/advanced/JetpackComposeDemo/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ
diff --git a/kotlin/advanced/JetpackComposeDemo/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/kotlin/advanced/JetpackComposeDemo/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
new file mode 100644
index 000000000..1b9a6956b
Binary files /dev/null and b/kotlin/advanced/JetpackComposeDemo/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ
diff --git a/kotlin/advanced/JetpackComposeDemo/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/kotlin/advanced/JetpackComposeDemo/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 000000000..28d4b77f9
Binary files /dev/null and b/kotlin/advanced/JetpackComposeDemo/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ
diff --git a/kotlin/advanced/JetpackComposeDemo/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/kotlin/advanced/JetpackComposeDemo/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
new file mode 100644
index 000000000..9287f5083
Binary files /dev/null and b/kotlin/advanced/JetpackComposeDemo/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ
diff --git a/kotlin/advanced/JetpackComposeDemo/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/kotlin/advanced/JetpackComposeDemo/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 000000000..aa7d6427e
Binary files /dev/null and b/kotlin/advanced/JetpackComposeDemo/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ
diff --git a/kotlin/advanced/JetpackComposeDemo/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/kotlin/advanced/JetpackComposeDemo/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
new file mode 100644
index 000000000..9126ae37c
Binary files /dev/null and b/kotlin/advanced/JetpackComposeDemo/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ
diff --git a/kotlin/advanced/JetpackComposeDemo/app/src/main/res/values/colors.xml b/kotlin/advanced/JetpackComposeDemo/app/src/main/res/values/colors.xml
new file mode 100644
index 000000000..758655a2e
--- /dev/null
+++ b/kotlin/advanced/JetpackComposeDemo/app/src/main/res/values/colors.xml
@@ -0,0 +1,10 @@
+
+
+ #FFBB86FC
+ #FF6200EE
+ #FF3700B3
+ #FF03DAC5
+ #FF018786
+ #FF000000
+ #FFFFFFFF
+
diff --git a/kotlin/advanced/JetpackComposeDemo/app/src/main/res/values/strings.xml b/kotlin/advanced/JetpackComposeDemo/app/src/main/res/values/strings.xml
new file mode 100644
index 000000000..532b5c7db
--- /dev/null
+++ b/kotlin/advanced/JetpackComposeDemo/app/src/main/res/values/strings.xml
@@ -0,0 +1,14 @@
+
+ Jetpack Compose Demo
+ Google Mobile Ads Sample
+ Google Mobile Ads SDK is not initialized.
+ Google Mobile Ads SDK requires consent.
+ Google Mobile Ads SDK obtained consent.
+ Google Mobile Ads SDK has a consent error.
+ Google Mobile Ads SDK has a consent form error.
+ Google Mobile Ads SDK is initialized.
+ Show Privacy Options Form
+ Failed to open ad inspector.
+ Failed to open ad inspector with error: %1$s
+ Ad Inspector
+
diff --git a/kotlin/advanced/JetpackComposeDemo/app/src/main/res/values/themes.xml b/kotlin/advanced/JetpackComposeDemo/app/src/main/res/values/themes.xml
new file mode 100644
index 000000000..4e137a470
--- /dev/null
+++ b/kotlin/advanced/JetpackComposeDemo/app/src/main/res/values/themes.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/kotlin/advanced/JetpackComposeDemo/build.gradle.kts b/kotlin/advanced/JetpackComposeDemo/build.gradle.kts
new file mode 100644
index 000000000..09709da45
--- /dev/null
+++ b/kotlin/advanced/JetpackComposeDemo/build.gradle.kts
@@ -0,0 +1,7 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+plugins {
+ id("com.android.application") version "8.2.2" apply false
+ id("org.jetbrains.kotlin.android") version "1.9.0" apply false
+}
+
+tasks.register("clean", Delete::class) { delete(rootProject.buildDir) }
diff --git a/kotlin/advanced/JetpackComposeDemo/gradle.properties b/kotlin/advanced/JetpackComposeDemo/gradle.properties
new file mode 100644
index 000000000..2cbd6d19d
--- /dev/null
+++ b/kotlin/advanced/JetpackComposeDemo/gradle.properties
@@ -0,0 +1,23 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app's APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Kotlin code style for this project: "official" or "obsolete":
+kotlin.code.style=official
+# Enables namespacing of each library's R class so that its R class includes only the
+# resources declared in the library itself and none from the library's dependencies,
+# thereby reducing the size of the R class for that library
+android.nonTransitiveRClass=true
diff --git a/kotlin/advanced/JetpackComposeDemo/gradle/wrapper/gradle-wrapper.properties b/kotlin/advanced/JetpackComposeDemo/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..30ec418c7
--- /dev/null
+++ b/kotlin/advanced/JetpackComposeDemo/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Wed Apr 03 14:18:00 PDT 2024
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/kotlin/advanced/JetpackComposeDemo/gradlew b/kotlin/advanced/JetpackComposeDemo/gradlew
new file mode 100644
index 000000000..9d82f7891
--- /dev/null
+++ b/kotlin/advanced/JetpackComposeDemo/gradlew
@@ -0,0 +1,160 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/kotlin/advanced/JetpackComposeDemo/gradlew.bat b/kotlin/advanced/JetpackComposeDemo/gradlew.bat
new file mode 100644
index 000000000..aec99730b
--- /dev/null
+++ b/kotlin/advanced/JetpackComposeDemo/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/kotlin/advanced/JetpackComposeDemo/settings.gradle.kts b/kotlin/advanced/JetpackComposeDemo/settings.gradle.kts
new file mode 100644
index 000000000..f296a6be8
--- /dev/null
+++ b/kotlin/advanced/JetpackComposeDemo/settings.gradle.kts
@@ -0,0 +1,19 @@
+pluginManagement {
+ repositories {
+ google()
+ mavenCentral()
+ gradlePluginPortal()
+ }
+}
+
+dependencyResolutionManagement {
+ repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+rootProject.name = "Jetpack"
+
+include(":app")