From 7471c14d66d77a385103038a0cf2cceb774fce1d Mon Sep 17 00:00:00 2001 From: Teodor Grigor Date: Fri, 27 Oct 2023 14:36:23 +0300 Subject: [PATCH 01/10] Added Composable RatingBar Component --- ui/designsystem/api/designsystem.api | 4 ++ .../teogor/ceres/ui/designsystem/RatingBar.kt | 72 +++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 ui/designsystem/src/main/kotlin/dev/teogor/ceres/ui/designsystem/RatingBar.kt diff --git a/ui/designsystem/api/designsystem.api b/ui/designsystem/api/designsystem.api index 4650f23b..2e50395c 100644 --- a/ui/designsystem/api/designsystem.api +++ b/ui/designsystem/api/designsystem.api @@ -399,6 +399,10 @@ public final class dev/teogor/ceres/ui/designsystem/ParsableTextKt { public static final fun ParsableText (Ljava/lang/String;Ljava/util/List;Lkotlin/jvm/functions/Function1;Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;II)V } +public final class dev/teogor/ceres/ui/designsystem/RatingBarKt { + public static final fun RatingBar-q9LK7_k (Landroidx/compose/ui/Modifier;DIJFLandroidx/compose/runtime/Composer;II)V +} + public final class dev/teogor/ceres/ui/designsystem/SegmentedButtonsKt { public static final fun SegmentedButtons (Ljava/util/List;ILkotlin/jvm/functions/Function1;Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;II)V } diff --git a/ui/designsystem/src/main/kotlin/dev/teogor/ceres/ui/designsystem/RatingBar.kt b/ui/designsystem/src/main/kotlin/dev/teogor/ceres/ui/designsystem/RatingBar.kt new file mode 100644 index 00000000..8952b813 --- /dev/null +++ b/ui/designsystem/src/main/kotlin/dev/teogor/ceres/ui/designsystem/RatingBar.kt @@ -0,0 +1,72 @@ +/* + * Copyright 2023 teogor (Teodor Grigor) + * + * 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 dev.teogor.ceres.ui.designsystem + +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.size +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.outlined.StarHalf +import androidx.compose.material.icons.outlined.Star +import androidx.compose.material.icons.outlined.StarOutline +import androidx.compose.material3.Icon +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import dev.teogor.ceres.ui.designsystem.core.ColorUtils.harmonizeWithPrimary +import kotlin.math.ceil +import kotlin.math.floor + +@Composable +fun RatingBar( + modifier: Modifier = Modifier, + rating: Double = 0.0, + stars: Int = 5, + starsColor: Color = Color.Yellow.harmonizeWithPrimary(fraction = .4f), + starSize: Dp = 24.dp, +) { + val filledStars = floor(rating).toInt() + val unfilledStars = (stars - ceil(rating)).toInt() + val halfStar = !(rating.rem(1).equals(0.0)) + Row(modifier = modifier) { + repeat(filledStars) { + Icon( + imageVector = Icons.Outlined.Star, + contentDescription = null, + tint = starsColor, + modifier = Modifier.size(starSize), + ) + } + if (halfStar) { + Icon( + imageVector = Icons.AutoMirrored.Outlined.StarHalf, + contentDescription = null, + tint = starsColor, + modifier = Modifier.size(starSize), + ) + } + repeat(unfilledStars) { + Icon( + imageVector = Icons.Outlined.StarOutline, + contentDescription = null, + tint = starsColor, + modifier = Modifier.size(starSize), + ) + } + } +} From 67773602fca026ee934ace8db8ff4367dcd5213b Mon Sep 17 00:00:00 2001 From: Teodor Grigor Date: Fri, 27 Oct 2023 14:36:48 +0300 Subject: [PATCH 02/10] Enhance Flow for Native Ad Declaration in Compose --- .../dev/teogor/ceres/ads/HomeNativeAd.kt | 263 ++++++++++++++++++ .../kotlin/dev/teogor/ceres/di/AdMobModule.kt | 34 +++ .../teogor/ceres/feature/home/HomeScreen.kt | 71 +---- monetisation/admob/api/admob.api | 30 ++ .../monetisation/admob/annotations/AdField.kt | 21 ++ .../admob/annotations/AdOptions.kt | 21 ++ .../admob/annotations/AdProperty.kt | 21 ++ .../monetisation/admob/annotations/AdUI.kt | 21 ++ .../admob/formats/nativead/NativeAdData.kt | 32 +++ .../formats/nativead/NativeAdViewModel.kt | 33 +++ .../formats/nativead/createDefaultNativeAd.kt | 31 +++ 11 files changed, 511 insertions(+), 67 deletions(-) create mode 100644 app/src/main/kotlin/dev/teogor/ceres/ads/HomeNativeAd.kt create mode 100644 app/src/main/kotlin/dev/teogor/ceres/di/AdMobModule.kt create mode 100644 monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/annotations/AdField.kt create mode 100644 monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/annotations/AdOptions.kt create mode 100644 monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/annotations/AdProperty.kt create mode 100644 monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/annotations/AdUI.kt create mode 100644 monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdData.kt create mode 100644 monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdViewModel.kt create mode 100644 monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/formats/nativead/createDefaultNativeAd.kt diff --git a/app/src/main/kotlin/dev/teogor/ceres/ads/HomeNativeAd.kt b/app/src/main/kotlin/dev/teogor/ceres/ads/HomeNativeAd.kt new file mode 100644 index 00000000..44198f9b --- /dev/null +++ b/app/src/main/kotlin/dev/teogor/ceres/ads/HomeNativeAd.kt @@ -0,0 +1,263 @@ +/* + * Copyright 2023 teogor (Teodor Grigor) + * + * 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 dev.teogor.ceres.ads + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.ButtonDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.viewinterop.AndroidView +import androidx.hilt.navigation.compose.hiltViewModel +import com.google.android.gms.ads.nativead.NativeAd +import com.skydoves.landscapist.ImageOptions +import com.skydoves.landscapist.glide.GlideImage +import dagger.hilt.android.lifecycle.HiltViewModel +import dev.teogor.ceres.framework.core.compositions.LocalNetworkConnectivity +import dev.teogor.ceres.monetisation.admob.DemoAdUnitIds +import dev.teogor.ceres.monetisation.admob.annotations.AdOptions +import dev.teogor.ceres.monetisation.admob.annotations.AdProperty +import dev.teogor.ceres.monetisation.admob.annotations.AdUI +import dev.teogor.ceres.monetisation.admob.formats.nativead.AdLoaderConfig +import dev.teogor.ceres.monetisation.admob.formats.nativead.NativeAd +import dev.teogor.ceres.monetisation.admob.formats.nativead.NativeAdConfig +import dev.teogor.ceres.monetisation.admob.formats.nativead.NativeAdData +import dev.teogor.ceres.monetisation.admob.formats.nativead.NativeAdViewModel +import dev.teogor.ceres.monetisation.admob.formats.nativead.createBodyView +import dev.teogor.ceres.monetisation.admob.formats.nativead.createCallToActionView +import dev.teogor.ceres.monetisation.admob.formats.nativead.createHeadlineView +import dev.teogor.ceres.monetisation.admob.formats.nativead.createIconView +import dev.teogor.ceres.monetisation.admob.formats.nativead.createStarRatingView +import dev.teogor.ceres.ui.designsystem.RatingBar +import dev.teogor.ceres.ui.designsystem.Text +import dev.teogor.ceres.ui.theme.MaterialTheme +import javax.inject.Inject +import javax.inject.Singleton + +@Composable +fun HomeNativeAd( + homeNativeAdVM: HomeNativeAdVM = hiltViewModel(), +) { + val networkConnectivity = LocalNetworkConnectivity.current + val isOffline by remember { + derivedStateOf { networkConnectivity.isOffline } + } + val adId = DemoAdUnitIds.NATIVE + val nativeAd by remember { homeNativeAdVM.nativeAd } + if (!isOffline) { + val nativeAdConfig = homeNativeAdConfig() + + NativeAd( + modifier = Modifier.background( + color = MaterialTheme.colorScheme.primaryContainer, + shape = RoundedCornerShape(20.dp), + ), + nativeAdConfig = nativeAdConfig, + adContent = { + HomeNativeAdUI(nativeAdConfig) + }, + nativeAd = nativeAd, + config = AdLoaderConfig(adId), + refreshIntervalMillis = 30_000L, + onAdLoaded = { + homeNativeAdVM.setNativeAd(it) + }, + ) + } +} + +@AdProperty +@Singleton +data class HomeNativeAdData @Inject constructor( + override val nativeAd: MutableState, +) : NativeAdData(nativeAd) + +@HiltViewModel +class HomeNativeAdVM @Inject constructor( + homeNativeAdData: HomeNativeAdData, +) : NativeAdViewModel(homeNativeAdData) + +@AdOptions +@Composable +fun homeNativeAdConfig() = NativeAdConfig.Builder() + .headlineView( + createHeadlineView { + Text( + text = if (it.length > 25) it.take(25) else it, + color = MaterialTheme.colorScheme.onPrimaryContainer, + fontSize = 18.sp, + lineHeight = 20.sp, + ) + }, + ) + .bodyView( + createBodyView { + Text( + text = if (it.length > 90) it.take(90) else it, + color = MaterialTheme.colorScheme.onPrimaryContainer, + fontSize = 10.sp, + lineHeight = 12.sp, + ) + }, + ) + .starRatingView( + createStarRatingView { + RatingBar( + rating = it, + starSize = 18.dp, + ) + }, + ) + .callToActionView( + createCallToActionView { + Text( + text = it, + color = MaterialTheme.colorScheme.onPrimary, + fontSize = 12.sp, + modifier = Modifier + .padding(top = 4.dp) + .background( + color = MaterialTheme.colorScheme.primary, + shape = ButtonDefaults.shape, + ) + .padding(horizontal = 24.dp, vertical = 8.dp), + ) + }, + ) + .iconView( + createIconView { + GlideImage( + modifier = Modifier + .size(50.dp) + .background( + color = MaterialTheme.colorScheme.background.copy(alpha = .2f), + shape = RoundedCornerShape(10.dp), + ) + .clip(RoundedCornerShape(10.dp)) + .padding(6.dp), + imageModel = { it.uri }, + imageOptions = ImageOptions( + contentScale = ContentScale.Crop, + alignment = Alignment.Center, + ), + ) + }, + ) + .build() + +@AdUI +@Composable +fun HomeNativeAdUI( + nativeAdConfig: NativeAdConfig, +) { + val advertiserView = nativeAdConfig.advertiserView + val adChoicesView = nativeAdConfig.adChoicesView + val headlineView = nativeAdConfig.headlineView + val bodyView = nativeAdConfig.bodyView + val callToActionView = nativeAdConfig.callToActionView + val iconView = nativeAdConfig.iconView + val imageView = nativeAdConfig.imageView + val mediaView = nativeAdConfig.mediaView + val priceView = nativeAdConfig.priceView + val starRatingView = nativeAdConfig.starRatingView + val storeView = nativeAdConfig.storeView + + Box { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 10.dp, horizontal = 6.dp), + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + ) { + iconView?.let { iconView -> + AndroidView( + factory = { + iconView.composeView + }, + modifier = Modifier.align(Alignment.Top), + ) + } + Column( + modifier = Modifier.padding(start = 6.dp), + ) { + headlineView?.let { headlineView -> + AndroidView( + factory = { + headlineView.composeView + }, + ) + } + starRatingView?.let { starRatingView -> + AndroidView( + factory = { + starRatingView.composeView + }, + ) + } + bodyView?.let { bodyView -> + AndroidView( + factory = { + bodyView.composeView + }, + ) + } + } + } + callToActionView?.let { callToActionView -> + AndroidView( + factory = { + callToActionView.composeView + }, + modifier = Modifier.align(Alignment.CenterHorizontally), + ) + } + } + + Text( + text = "AD", + fontSize = 10.sp, + lineHeight = 10.sp, + color = MaterialTheme.colorScheme.onSecondaryContainer, + modifier = Modifier + .align(Alignment.BottomEnd) + .background( + color = MaterialTheme.colorScheme.secondaryContainer, + shape = CircleShape, + ) + .padding(horizontal = 8.dp, vertical = 8.dp) + .padding(end = 2.dp, bottom = 2.dp), + ) + } +} diff --git a/app/src/main/kotlin/dev/teogor/ceres/di/AdMobModule.kt b/app/src/main/kotlin/dev/teogor/ceres/di/AdMobModule.kt new file mode 100644 index 00000000..1663b376 --- /dev/null +++ b/app/src/main/kotlin/dev/teogor/ceres/di/AdMobModule.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2023 teogor (Teodor Grigor) + * + * 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 dev.teogor.ceres.di + +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import dev.teogor.ceres.ads.HomeNativeAdData +import dev.teogor.ceres.monetisation.admob.formats.nativead.createDefaultNativeAd +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object AdMobModule { + + @Provides + @Singleton + fun provideHomeNativeAdData() = createDefaultNativeAd() +} diff --git a/app/src/main/kotlin/dev/teogor/ceres/feature/home/HomeScreen.kt b/app/src/main/kotlin/dev/teogor/ceres/feature/home/HomeScreen.kt index ffb90d08..a98afbf5 100644 --- a/app/src/main/kotlin/dev/teogor/ceres/feature/home/HomeScreen.kt +++ b/app/src/main/kotlin/dev/teogor/ceres/feature/home/HomeScreen.kt @@ -18,22 +18,17 @@ package dev.teogor.ceres.feature.home import androidx.compose.foundation.background import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp import androidx.hilt.navigation.compose.hiltViewModel -import com.skydoves.landscapist.ImageOptions -import com.skydoves.landscapist.glide.GlideImage import dev.teogor.ceres.R +import dev.teogor.ceres.ads.HomeNativeAdUI +import dev.teogor.ceres.ads.homeNativeAdConfig import dev.teogor.ceres.core.foundation.extensions.createMediaPlayer import dev.teogor.ceres.data.datastore.defaults.ceresPreferences import dev.teogor.ceres.framework.core.app.BaseActions @@ -49,12 +44,6 @@ import dev.teogor.ceres.framework.core.screen.toolbarTokens import dev.teogor.ceres.monetisation.admob.DemoAdUnitIds import dev.teogor.ceres.monetisation.admob.formats.nativead.AdLoaderConfig import dev.teogor.ceres.monetisation.admob.formats.nativead.NativeAd -import dev.teogor.ceres.monetisation.admob.formats.nativead.NativeAdConfig -import dev.teogor.ceres.monetisation.admob.formats.nativead.NativeAdUi -import dev.teogor.ceres.monetisation.admob.formats.nativead.createBodyView -import dev.teogor.ceres.monetisation.admob.formats.nativead.createCallToActionView -import dev.teogor.ceres.monetisation.admob.formats.nativead.createHeadlineView -import dev.teogor.ceres.monetisation.admob.formats.nativead.createIconView import dev.teogor.ceres.monetisation.ads.ExperimentalAdsControlApi import dev.teogor.ceres.monetisation.messaging.ConsentManager import dev.teogor.ceres.navigation.core.LocalNavigationParameters @@ -64,7 +53,6 @@ import dev.teogor.ceres.screen.builder.compose.SimpleView import dev.teogor.ceres.screen.core.layout.ColumnLayoutBase import dev.teogor.ceres.screen.ui.api.ExperimentalOnboardingScreenApi import dev.teogor.ceres.screen.ui.onboarding.OnboardingRoute -import dev.teogor.ceres.ui.designsystem.Text import dev.teogor.ceres.ui.theme.MaterialTheme // todo better way to configure this. perhaps use kotlin builder syntax @@ -180,7 +168,7 @@ private fun HomeScreen( homeVM.nativeAd } if (!isOffline) { - val nativeAdConfig = nativeAdConfig() + val nativeAdConfig = homeNativeAdConfig() NativeAd( modifier = Modifier @@ -192,7 +180,7 @@ private fun HomeScreen( .padding(horizontal = 6.dp, vertical = 10.dp), nativeAdConfig = nativeAdConfig, adContent = { - NativeAdUi(nativeAdConfig) + HomeNativeAdUI(nativeAdConfig) }, nativeAd = nativeAd, config = AdLoaderConfig(adId), @@ -203,54 +191,3 @@ private fun HomeScreen( ) } } - -@Composable -fun nativeAdConfig() = NativeAdConfig.Builder() - .headlineView( - createHeadlineView { - Text( - text = it, - color = MaterialTheme.colorScheme.onPrimaryContainer, - fontSize = 18.sp, - ) - }, - ) - .bodyView( - createBodyView { - Text( - text = it, - color = MaterialTheme.colorScheme.onPrimaryContainer, - fontSize = 10.sp, - maxLines = 1, - ) - }, - ) - .callToActionView( - createCallToActionView { - Text( - text = it, - color = MaterialTheme.colorScheme.onPrimary, - fontSize = 12.sp, - ) - }, - ) - .iconView( - createIconView { - GlideImage( - modifier = Modifier - .size(50.dp) - .background( - color = MaterialTheme.colorScheme.background.copy(alpha = .2f), - shape = RoundedCornerShape(10.dp), - ) - .clip(RoundedCornerShape(10.dp)) - .padding(6.dp), - imageModel = { it.uri }, // loading a network image using an URL. - imageOptions = ImageOptions( - contentScale = ContentScale.Crop, - alignment = Alignment.Center, - ), - ) - }, - ) - .build() diff --git a/monetisation/admob/api/admob.api b/monetisation/admob/api/admob.api index 73602f94..0eedc159 100644 --- a/monetisation/admob/api/admob.api +++ b/monetisation/admob/api/admob.api @@ -50,6 +50,18 @@ public final class dev/teogor/ceres/monetisation/admob/Logger$DefaultImpls { public static fun log (Ldev/teogor/ceres/monetisation/admob/Logger;Ljava/lang/String;)V } +public abstract interface annotation class dev/teogor/ceres/monetisation/admob/annotations/AdField : java/lang/annotation/Annotation { +} + +public abstract interface annotation class dev/teogor/ceres/monetisation/admob/annotations/AdOptions : java/lang/annotation/Annotation { +} + +public abstract interface annotation class dev/teogor/ceres/monetisation/admob/annotations/AdProperty : java/lang/annotation/Annotation { +} + +public abstract interface annotation class dev/teogor/ceres/monetisation/admob/annotations/AdUI : java/lang/annotation/Annotation { +} + public final class dev/teogor/ceres/monetisation/admob/databinding/AdmobNativeBinding : androidx/viewbinding/ViewBinding { public final field composeView Landroidx/compose/ui/platform/ComposeView; public final field parentNative Lcom/google/android/gms/ads/nativead/NativeAdView; @@ -510,6 +522,17 @@ public final class dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAd public fun toString ()Ljava/lang/String; } +public abstract class dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdData { + public static final field $stable I + public fun (Landroidx/compose/runtime/MutableState;)V + public fun getNativeAd ()Landroidx/compose/runtime/MutableState; + public final fun getNativeAdData ()Landroidx/compose/runtime/State; +} + +public final class dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdDataKt { + public static final fun setNativeAd (Ldev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdData;Lcom/google/android/gms/ads/nativead/NativeAd;)V +} + public final class dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdKt { public static final fun NativeAd (Landroidx/compose/ui/Modifier;Ldev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdConfig;Lkotlin/jvm/functions/Function2;Lcom/google/android/gms/ads/nativead/NativeAd;Ldev/teogor/ceres/monetisation/admob/formats/nativead/AdLoaderConfig;JLkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V } @@ -518,6 +541,13 @@ public final class dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAd public static final fun NativeAdUi (Ldev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdConfig;Landroidx/compose/runtime/Composer;I)V } +public abstract class dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdViewModel : androidx/lifecycle/ViewModel { + public static final field $stable I + public fun (Ldev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdData;)V + public final fun getNativeAd ()Landroidx/compose/runtime/State; + public final fun setNativeAd (Lcom/google/android/gms/ads/nativead/NativeAd;)V +} + public final class dev/teogor/ceres/monetisation/admob/formats/nativead/RefreshableNativeAdKt { public static final fun RefreshableNativeAd (Ljava/lang/String;Lcom/google/android/gms/ads/AdLoader;JLandroidx/compose/runtime/Composer;I)V } diff --git a/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/annotations/AdField.kt b/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/annotations/AdField.kt new file mode 100644 index 00000000..8e26e75b --- /dev/null +++ b/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/annotations/AdField.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2023 teogor (Teodor Grigor) + * + * 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 dev.teogor.ceres.monetisation.admob.annotations + +@Target(AnnotationTarget.PROPERTY, AnnotationTarget.FUNCTION) +@Retention(AnnotationRetention.RUNTIME) +annotation class AdField diff --git a/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/annotations/AdOptions.kt b/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/annotations/AdOptions.kt new file mode 100644 index 00000000..e59cd051 --- /dev/null +++ b/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/annotations/AdOptions.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2023 teogor (Teodor Grigor) + * + * 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 dev.teogor.ceres.monetisation.admob.annotations + +@Target(AnnotationTarget.FUNCTION) +@Retention(AnnotationRetention.RUNTIME) +annotation class AdOptions diff --git a/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/annotations/AdProperty.kt b/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/annotations/AdProperty.kt new file mode 100644 index 00000000..3f156bfc --- /dev/null +++ b/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/annotations/AdProperty.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2023 teogor (Teodor Grigor) + * + * 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 dev.teogor.ceres.monetisation.admob.annotations + +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +annotation class AdProperty diff --git a/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/annotations/AdUI.kt b/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/annotations/AdUI.kt new file mode 100644 index 00000000..ebae971d --- /dev/null +++ b/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/annotations/AdUI.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2023 teogor (Teodor Grigor) + * + * 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 dev.teogor.ceres.monetisation.admob.annotations + +@Target(AnnotationTarget.FUNCTION) +@Retention(AnnotationRetention.RUNTIME) +annotation class AdUI diff --git a/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdData.kt b/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdData.kt new file mode 100644 index 00000000..e1748716 --- /dev/null +++ b/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdData.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2023 teogor (Teodor Grigor) + * + * 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 dev.teogor.ceres.monetisation.admob.formats.nativead + +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.State +import androidx.compose.runtime.derivedStateOf +import com.google.android.gms.ads.nativead.NativeAd + +abstract class NativeAdData( + open val nativeAd: MutableState, +) { + val nativeAdData: State = derivedStateOf { nativeAd.value } +} + +fun NativeAdData.setNativeAd(ad: NativeAd) { + nativeAd.value = ad +} diff --git a/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdViewModel.kt b/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdViewModel.kt new file mode 100644 index 00000000..40478c4f --- /dev/null +++ b/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdViewModel.kt @@ -0,0 +1,33 @@ +/* + * Copyright 2023 teogor (Teodor Grigor) + * + * 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 dev.teogor.ceres.monetisation.admob.formats.nativead + +import androidx.lifecycle.ViewModel +import com.google.android.gms.ads.nativead.NativeAd +import dev.teogor.ceres.monetisation.admob.annotations.AdField + +abstract class NativeAdViewModel( + private val nativeAdData: NativeAdData, +) : ViewModel() { + @AdField + val nativeAd = nativeAdData.nativeAdData + + @AdField + fun setNativeAd(ad: NativeAd) { + nativeAdData.setNativeAd(ad) + } +} diff --git a/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/formats/nativead/createDefaultNativeAd.kt b/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/formats/nativead/createDefaultNativeAd.kt new file mode 100644 index 00000000..8cafb241 --- /dev/null +++ b/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/formats/nativead/createDefaultNativeAd.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2023 teogor (Teodor Grigor) + * + * 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 dev.teogor.ceres.monetisation.admob.formats.nativead + +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf + +/** + * Creates a new instance of a NativeAdData subclass, initialized with a default `MutableState` of `null`. + * + * @return A new instance of the specified NativeAdData subclass. + */ +inline fun createDefaultNativeAd(): T { + return T::class.java + .getDeclaredConstructor(MutableState::class.java) + .newInstance(mutableStateOf(null)) +} From c8cbee05d2dcccc5d9ebb1daf2c73805bca0363e Mon Sep 17 00:00:00 2001 From: Teodor Grigor Date: Fri, 27 Oct 2023 15:06:37 +0300 Subject: [PATCH 03/10] Refactor AdLoaderConfig and Add NativeAdOptions.Builder Extensions --- .../dev/teogor/ceres/ads/HomeNativeAd.kt | 34 +++--- .../teogor/ceres/feature/home/HomeScreen.kt | 48 +++----- .../ceres/feature/home/HomeViewModel.kt | 12 +- monetisation/admob/api/admob.api | 78 +++++++++---- .../admob/formats/nativead/AdLoader.kt | 2 +- .../admob/formats/nativead/AdLoaderConfig.kt | 107 ++++++++++++++++-- .../admob/formats/nativead/NativeAd.kt | 8 +- .../admob/formats/nativead/NativeAdUi.kt | 95 ---------------- .../admob/models/AdChoicesPlacement.kt | 24 ++++ .../admob/models/NativeMediaAspectRatio.kt | 25 ++++ .../admob/models/SwipeGestureDirection.kt | 24 ++++ 11 files changed, 262 insertions(+), 195 deletions(-) delete mode 100644 monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdUi.kt create mode 100644 monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/models/AdChoicesPlacement.kt create mode 100644 monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/models/NativeMediaAspectRatio.kt create mode 100644 monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/models/SwipeGestureDirection.kt diff --git a/app/src/main/kotlin/dev/teogor/ceres/ads/HomeNativeAd.kt b/app/src/main/kotlin/dev/teogor/ceres/ads/HomeNativeAd.kt index 44198f9b..9e2d7687 100644 --- a/app/src/main/kotlin/dev/teogor/ceres/ads/HomeNativeAd.kt +++ b/app/src/main/kotlin/dev/teogor/ceres/ads/HomeNativeAd.kt @@ -48,7 +48,6 @@ import dev.teogor.ceres.monetisation.admob.DemoAdUnitIds import dev.teogor.ceres.monetisation.admob.annotations.AdOptions import dev.teogor.ceres.monetisation.admob.annotations.AdProperty import dev.teogor.ceres.monetisation.admob.annotations.AdUI -import dev.teogor.ceres.monetisation.admob.formats.nativead.AdLoaderConfig import dev.teogor.ceres.monetisation.admob.formats.nativead.NativeAd import dev.teogor.ceres.monetisation.admob.formats.nativead.NativeAdConfig import dev.teogor.ceres.monetisation.admob.formats.nativead.NativeAdData @@ -58,12 +57,24 @@ import dev.teogor.ceres.monetisation.admob.formats.nativead.createCallToActionVi import dev.teogor.ceres.monetisation.admob.formats.nativead.createHeadlineView import dev.teogor.ceres.monetisation.admob.formats.nativead.createIconView import dev.teogor.ceres.monetisation.admob.formats.nativead.createStarRatingView +import dev.teogor.ceres.monetisation.admob.formats.nativead.defaultAdLoaderConfig import dev.teogor.ceres.ui.designsystem.RatingBar import dev.teogor.ceres.ui.designsystem.Text import dev.teogor.ceres.ui.theme.MaterialTheme import javax.inject.Inject import javax.inject.Singleton +@AdProperty +@Singleton +data class HomeNativeAdData @Inject constructor( + override val nativeAd: MutableState, +) : NativeAdData(nativeAd) + +@HiltViewModel +class HomeNativeAdVM @Inject constructor( + homeNativeAdData: HomeNativeAdData, +) : NativeAdViewModel(homeNativeAdData) + @Composable fun HomeNativeAd( homeNativeAdVM: HomeNativeAdVM = hiltViewModel(), @@ -75,19 +86,15 @@ fun HomeNativeAd( val adId = DemoAdUnitIds.NATIVE val nativeAd by remember { homeNativeAdVM.nativeAd } if (!isOffline) { - val nativeAdConfig = homeNativeAdConfig() - NativeAd( modifier = Modifier.background( color = MaterialTheme.colorScheme.primaryContainer, shape = RoundedCornerShape(20.dp), ), - nativeAdConfig = nativeAdConfig, - adContent = { - HomeNativeAdUI(nativeAdConfig) - }, + nativeAdConfig = homeNativeAdConfig(), + adContent = { HomeNativeAdUI(it) }, nativeAd = nativeAd, - config = AdLoaderConfig(adId), + config = defaultAdLoaderConfig(adId), refreshIntervalMillis = 30_000L, onAdLoaded = { homeNativeAdVM.setNativeAd(it) @@ -96,17 +103,6 @@ fun HomeNativeAd( } } -@AdProperty -@Singleton -data class HomeNativeAdData @Inject constructor( - override val nativeAd: MutableState, -) : NativeAdData(nativeAd) - -@HiltViewModel -class HomeNativeAdVM @Inject constructor( - homeNativeAdData: HomeNativeAdData, -) : NativeAdViewModel(homeNativeAdData) - @AdOptions @Composable fun homeNativeAdConfig() = NativeAdConfig.Builder() diff --git a/app/src/main/kotlin/dev/teogor/ceres/feature/home/HomeScreen.kt b/app/src/main/kotlin/dev/teogor/ceres/feature/home/HomeScreen.kt index a98afbf5..b0154abc 100644 --- a/app/src/main/kotlin/dev/teogor/ceres/feature/home/HomeScreen.kt +++ b/app/src/main/kotlin/dev/teogor/ceres/feature/home/HomeScreen.kt @@ -16,19 +16,16 @@ package dev.teogor.ceres.feature.home -import androidx.compose.foundation.background +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import dev.teogor.ceres.R -import dev.teogor.ceres.ads.HomeNativeAdUI -import dev.teogor.ceres.ads.homeNativeAdConfig +import dev.teogor.ceres.ads.HomeNativeAd import dev.teogor.ceres.core.foundation.extensions.createMediaPlayer import dev.teogor.ceres.data.datastore.defaults.ceresPreferences import dev.teogor.ceres.framework.core.app.BaseActions @@ -41,9 +38,6 @@ import dev.teogor.ceres.framework.core.screen.showNavBar import dev.teogor.ceres.framework.core.screen.showSettingsButton import dev.teogor.ceres.framework.core.screen.toolbarTitle import dev.teogor.ceres.framework.core.screen.toolbarTokens -import dev.teogor.ceres.monetisation.admob.DemoAdUnitIds -import dev.teogor.ceres.monetisation.admob.formats.nativead.AdLoaderConfig -import dev.teogor.ceres.monetisation.admob.formats.nativead.NativeAd import dev.teogor.ceres.monetisation.ads.ExperimentalAdsControlApi import dev.teogor.ceres.monetisation.messaging.ConsentManager import dev.teogor.ceres.navigation.core.LocalNavigationParameters @@ -53,7 +47,7 @@ import dev.teogor.ceres.screen.builder.compose.SimpleView import dev.teogor.ceres.screen.core.layout.ColumnLayoutBase import dev.teogor.ceres.screen.ui.api.ExperimentalOnboardingScreenApi import dev.teogor.ceres.screen.ui.onboarding.OnboardingRoute -import dev.teogor.ceres.ui.theme.MaterialTheme +import dev.teogor.ceres.ui.designsystem.Surface // todo better way to configure this. perhaps use kotlin builder syntax @Composable @@ -163,31 +157,15 @@ private fun HomeScreen( }, ) - val adId = DemoAdUnitIds.NATIVE - val nativeAd by remember { - homeVM.nativeAd - } - if (!isOffline) { - val nativeAdConfig = homeNativeAdConfig() - - NativeAd( - modifier = Modifier - .padding(horizontal = 10.dp) - .background( - color = MaterialTheme.colorScheme.primaryContainer, - shape = RoundedCornerShape(20.dp), - ) - .padding(horizontal = 6.dp, vertical = 10.dp), - nativeAdConfig = nativeAdConfig, - adContent = { - HomeNativeAdUI(nativeAdConfig) - }, - nativeAd = nativeAd, - config = AdLoaderConfig(adId), - refreshIntervalMillis = 30000L, - onAdLoaded = { - homeVM.setNativeAd(it) - }, - ) + Surface( + modifier = Modifier + .padding(horizontal = 10.dp) + .padding(vertical = 10.dp) + .fillMaxWidth(), + shape = RoundedCornerShape(20.dp), + tonalElevation = 6.dp, + shadowElevation = 4.dp, + ) { + HomeNativeAd() } } diff --git a/app/src/main/kotlin/dev/teogor/ceres/feature/home/HomeViewModel.kt b/app/src/main/kotlin/dev/teogor/ceres/feature/home/HomeViewModel.kt index 44c54f8a..5f8a5247 100644 --- a/app/src/main/kotlin/dev/teogor/ceres/feature/home/HomeViewModel.kt +++ b/app/src/main/kotlin/dev/teogor/ceres/feature/home/HomeViewModel.kt @@ -16,9 +16,7 @@ package dev.teogor.ceres.feature.home -import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel -import com.google.android.gms.ads.nativead.NativeAd import dagger.hilt.android.lifecycle.HiltViewModel import dev.teogor.ceres.ads.HomeBannerAd import dev.teogor.ceres.ads.HomeInterstitialAd @@ -32,12 +30,4 @@ class HomeViewModel @Inject constructor( val homeRewardedInterstitialAd: HomeRewardedInterstitialAd, val homeRewardedAd: HomeRewardedAd, val homeBannerAd: HomeBannerAd, -) : ViewModel() { - - var nativeAd = mutableStateOf(null) - private set - - fun setNativeAd(ad: NativeAd) { - nativeAd.value = ad - } -} +) : ViewModel() diff --git a/monetisation/admob/api/admob.api b/monetisation/admob/api/admob.api index 0eedc159..19ac84ae 100644 --- a/monetisation/admob/api/admob.api +++ b/monetisation/admob/api/admob.api @@ -396,35 +396,39 @@ public final class dev/teogor/ceres/monetisation/admob/formats/nativead/AdCompon public final class dev/teogor/ceres/monetisation/admob/formats/nativead/AdLoaderConfig { public static final field $stable I - public fun (Ljava/lang/String;IIZZZZZZ)V - public synthetic fun (Ljava/lang/String;IIZZZZZZILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()I - public final fun component3 ()I - public final fun component4 ()Z - public final fun component5 ()Z - public final fun component6 ()Z - public final fun component7 ()Z - public final fun component8 ()Z - public final fun component9 ()Z - public final fun copy (Ljava/lang/String;IIZZZZZZ)Ldev/teogor/ceres/monetisation/admob/formats/nativead/AdLoaderConfig; - public static synthetic fun copy$default (Ldev/teogor/ceres/monetisation/admob/formats/nativead/AdLoaderConfig;Ljava/lang/String;IIZZZZZZILjava/lang/Object;)Ldev/teogor/ceres/monetisation/admob/formats/nativead/AdLoaderConfig; - public fun equals (Ljava/lang/Object;)Z public final fun getAdChoicesPlacement ()I public final fun getAdId ()Ljava/lang/String; - public final fun getClickToExpandRequested ()Z - public final fun getCustomControlsRequested ()Z public final fun getMediaAspectRatio ()I public final fun getRequestCustomMuteThisAd ()Z public final fun getRequestMultipleImages ()Z public final fun getReturnUrlsForImageAssets ()Z - public final fun getStartMuted ()Z public final fun getVideoOptions ()Lcom/google/android/gms/ads/VideoOptions; +} + +public final class dev/teogor/ceres/monetisation/admob/formats/nativead/AdLoaderConfig$Builder { + public static final field $stable I + public fun ()V + public fun (Ljava/lang/String;IIZZZZZZ)V + public synthetic fun (Ljava/lang/String;IIZZZZZZILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun adChoicesPlacement (I)Ldev/teogor/ceres/monetisation/admob/formats/nativead/AdLoaderConfig$Builder; + public final fun adId (Ljava/lang/String;)Ldev/teogor/ceres/monetisation/admob/formats/nativead/AdLoaderConfig$Builder; + public final fun build ()Ldev/teogor/ceres/monetisation/admob/formats/nativead/AdLoaderConfig; + public final fun clickToExpandRequested (Z)Ldev/teogor/ceres/monetisation/admob/formats/nativead/AdLoaderConfig$Builder; + public final fun copy (Ljava/lang/String;IIZZZZZZ)Ldev/teogor/ceres/monetisation/admob/formats/nativead/AdLoaderConfig$Builder; + public static synthetic fun copy$default (Ldev/teogor/ceres/monetisation/admob/formats/nativead/AdLoaderConfig$Builder;Ljava/lang/String;IIZZZZZZILjava/lang/Object;)Ldev/teogor/ceres/monetisation/admob/formats/nativead/AdLoaderConfig$Builder; + public final fun customControlsRequested (Z)Ldev/teogor/ceres/monetisation/admob/formats/nativead/AdLoaderConfig$Builder; + public fun equals (Ljava/lang/Object;)Z public fun hashCode ()I + public final fun mediaAspectRatio (I)Ldev/teogor/ceres/monetisation/admob/formats/nativead/AdLoaderConfig$Builder; + public final fun requestCustomMuteThisAd (Z)Ldev/teogor/ceres/monetisation/admob/formats/nativead/AdLoaderConfig$Builder; + public final fun requestMultipleImages (Z)Ldev/teogor/ceres/monetisation/admob/formats/nativead/AdLoaderConfig$Builder; + public final fun returnUrlsForImageAssets (Z)Ldev/teogor/ceres/monetisation/admob/formats/nativead/AdLoaderConfig$Builder; + public final fun startMuted (Z)Ldev/teogor/ceres/monetisation/admob/formats/nativead/AdLoaderConfig$Builder; public fun toString ()Ljava/lang/String; } public final class dev/teogor/ceres/monetisation/admob/formats/nativead/AdLoaderConfigKt { + public static final fun defaultAdLoaderConfig (Ljava/lang/String;)Ldev/teogor/ceres/monetisation/admob/formats/nativead/AdLoaderConfig; public static final fun toNativeAdOptions (Ldev/teogor/ceres/monetisation/admob/formats/nativead/AdLoaderConfig;)Lcom/google/android/gms/ads/nativead/NativeAdOptions; } @@ -534,11 +538,7 @@ public final class dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAd } public final class dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdKt { - public static final fun NativeAd (Landroidx/compose/ui/Modifier;Ldev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdConfig;Lkotlin/jvm/functions/Function2;Lcom/google/android/gms/ads/nativead/NativeAd;Ldev/teogor/ceres/monetisation/admob/formats/nativead/AdLoaderConfig;JLkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V -} - -public final class dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdUiKt { - public static final fun NativeAdUi (Ldev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdConfig;Landroidx/compose/runtime/Composer;I)V + public static final fun NativeAd (Landroidx/compose/ui/Modifier;Ldev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdConfig;Lkotlin/jvm/functions/Function3;Lcom/google/android/gms/ads/nativead/NativeAd;Ldev/teogor/ceres/monetisation/admob/formats/nativead/AdLoaderConfig;JLkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V } public abstract class dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdViewModel : androidx/lifecycle/ViewModel { @@ -556,3 +556,37 @@ public final class dev/teogor/ceres/monetisation/admob/formats/ui/AdmobBannerKt public static final fun AdmobBanner (Landroidx/compose/ui/Modifier;Ldev/teogor/ceres/monetisation/admob/formats/BannerAd;Landroidx/compose/runtime/Composer;II)V } +public final class dev/teogor/ceres/monetisation/admob/models/AdChoicesPlacement : java/lang/Enum { + public static final field BOTTOM_LEFT Ldev/teogor/ceres/monetisation/admob/models/AdChoicesPlacement; + public static final field BOTTOM_RIGHT Ldev/teogor/ceres/monetisation/admob/models/AdChoicesPlacement; + public static final field TOP_LEFT Ldev/teogor/ceres/monetisation/admob/models/AdChoicesPlacement; + public static final field TOP_RIGHT Ldev/teogor/ceres/monetisation/admob/models/AdChoicesPlacement; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public final fun getValue ()I + public static fun valueOf (Ljava/lang/String;)Ldev/teogor/ceres/monetisation/admob/models/AdChoicesPlacement; + public static fun values ()[Ldev/teogor/ceres/monetisation/admob/models/AdChoicesPlacement; +} + +public final class dev/teogor/ceres/monetisation/admob/models/NativeMediaAspectRatio : java/lang/Enum { + public static final field ANY Ldev/teogor/ceres/monetisation/admob/models/NativeMediaAspectRatio; + public static final field LANDSCAPE Ldev/teogor/ceres/monetisation/admob/models/NativeMediaAspectRatio; + public static final field PORTRAIT Ldev/teogor/ceres/monetisation/admob/models/NativeMediaAspectRatio; + public static final field SQUARE Ldev/teogor/ceres/monetisation/admob/models/NativeMediaAspectRatio; + public static final field UNKNOWN Ldev/teogor/ceres/monetisation/admob/models/NativeMediaAspectRatio; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public final fun getValue ()I + public static fun valueOf (Ljava/lang/String;)Ldev/teogor/ceres/monetisation/admob/models/NativeMediaAspectRatio; + public static fun values ()[Ldev/teogor/ceres/monetisation/admob/models/NativeMediaAspectRatio; +} + +public final class dev/teogor/ceres/monetisation/admob/models/SwipeGestureDirection : java/lang/Enum { + public static final field DOWN Ldev/teogor/ceres/monetisation/admob/models/SwipeGestureDirection; + public static final field LEFT Ldev/teogor/ceres/monetisation/admob/models/SwipeGestureDirection; + public static final field RIGHT Ldev/teogor/ceres/monetisation/admob/models/SwipeGestureDirection; + public static final field UP Ldev/teogor/ceres/monetisation/admob/models/SwipeGestureDirection; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public final fun getValue ()I + public static fun valueOf (Ljava/lang/String;)Ldev/teogor/ceres/monetisation/admob/models/SwipeGestureDirection; + public static fun values ()[Ldev/teogor/ceres/monetisation/admob/models/SwipeGestureDirection; +} + diff --git a/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/formats/nativead/AdLoader.kt b/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/formats/nativead/AdLoader.kt index 57b82747..05dbf33f 100644 --- a/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/formats/nativead/AdLoader.kt +++ b/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/formats/nativead/AdLoader.kt @@ -27,7 +27,7 @@ import dev.teogor.ceres.monetisation.admob.formats.AdEvent @Composable fun rememberAdLoader( - config: AdLoaderConfig = AdLoaderConfig(""), + config: AdLoaderConfig, onAdEvent: (AdEvent) -> Unit = {}, onNativeAd: (NativeAd) -> Unit, ): AdLoader { diff --git a/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/formats/nativead/AdLoaderConfig.kt b/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/formats/nativead/AdLoaderConfig.kt index 3edb3175..a7855dd3 100644 --- a/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/formats/nativead/AdLoaderConfig.kt +++ b/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/formats/nativead/AdLoaderConfig.kt @@ -18,23 +18,92 @@ package dev.teogor.ceres.monetisation.admob.formats.nativead import com.google.android.gms.ads.VideoOptions import com.google.android.gms.ads.nativead.NativeAdOptions +import dev.teogor.ceres.monetisation.admob.models.AdChoicesPlacement +import dev.teogor.ceres.monetisation.admob.models.NativeMediaAspectRatio -data class AdLoaderConfig( +class AdLoaderConfig internal constructor( val adId: String, - val adChoicesPlacement: Int = NativeAdOptions.ADCHOICES_TOP_RIGHT, - val mediaAspectRatio: Int = NativeAdOptions.NATIVE_MEDIA_ASPECT_RATIO_PORTRAIT, - val requestCustomMuteThisAd: Boolean = false, - val requestMultipleImages: Boolean = false, - val returnUrlsForImageAssets: Boolean = false, - val clickToExpandRequested: Boolean = true, - val customControlsRequested: Boolean = false, - val startMuted: Boolean = false, + val adChoicesPlacement: AdChoicesPlacement, + val mediaAspectRatio: NativeMediaAspectRatio, + val requestCustomMuteThisAd: Boolean, + val requestMultipleImages: Boolean, + val returnUrlsForImageAssets: Boolean, + clickToExpandRequested: Boolean, + customControlsRequested: Boolean, + startMuted: Boolean, ) { val videoOptions = VideoOptions.Builder() .setClickToExpandRequested(clickToExpandRequested) .setCustomControlsRequested(customControlsRequested) .setStartMuted(startMuted) .build() + + // Nested builder class + data class Builder( + private var adId: String = "", + private var adChoicesPlacement: AdChoicesPlacement = AdChoicesPlacement.TOP_RIGHT, + private var mediaAspectRatio: NativeMediaAspectRatio = NativeMediaAspectRatio.PORTRAIT, + private var requestCustomMuteThisAd: Boolean = false, + private var requestMultipleImages: Boolean = false, + private var returnUrlsForImageAssets: Boolean = false, + private var clickToExpandRequested: Boolean = true, + private var customControlsRequested: Boolean = false, + private var startMuted: Boolean = false, + ) { + + fun adId(adId: String) = apply { + if (adId.isEmpty()) { + throw IllegalArgumentException("`adId` cannot be empty.") + } + this.adId = adId + } + + fun adChoicesPlacement(adChoicesPlacement: AdChoicesPlacement) = apply { + this.adChoicesPlacement = adChoicesPlacement + } + + fun mediaAspectRatio(mediaAspectRatio: NativeMediaAspectRatio) = apply { + this.mediaAspectRatio = mediaAspectRatio + } + + fun requestCustomMuteThisAd(requestCustomMuteThisAd: Boolean) = apply { + this.requestCustomMuteThisAd = requestCustomMuteThisAd + } + + fun requestMultipleImages(requestMultipleImages: Boolean) = apply { + this.requestMultipleImages = requestMultipleImages + } + + fun returnUrlsForImageAssets(returnUrlsForImageAssets: Boolean) = apply { + this.returnUrlsForImageAssets = returnUrlsForImageAssets + } + + fun clickToExpandRequested(clickToExpandRequested: Boolean) = apply { + this.clickToExpandRequested = clickToExpandRequested + } + + fun customControlsRequested(customControlsRequested: Boolean) = apply { + this.customControlsRequested = customControlsRequested + } + + fun startMuted(startMuted: Boolean) = apply { + this.startMuted = startMuted + } + + fun build(): AdLoaderConfig { + return AdLoaderConfig( + adId, + adChoicesPlacement, + mediaAspectRatio, + requestCustomMuteThisAd, + requestMultipleImages, + returnUrlsForImageAssets, + clickToExpandRequested, + customControlsRequested, + startMuted, + ) + } + } } fun AdLoaderConfig.toNativeAdOptions() = NativeAdOptions.Builder() @@ -44,4 +113,24 @@ fun AdLoaderConfig.toNativeAdOptions() = NativeAdOptions.Builder() .setRequestMultipleImages(requestMultipleImages) .setReturnUrlsForImageAssets(returnUrlsForImageAssets) .setVideoOptions(videoOptions) + // TODO enableCustomClickGestureDirection + // .enableCustomClickGestureDirection() + .build() + +fun NativeAdOptions.Builder.setAdChoicesPlacement( + adChoicesPlacement: AdChoicesPlacement, +): NativeAdOptions.Builder { + return setAdChoicesPlacement(adChoicesPlacement.value) +} + +fun NativeAdOptions.Builder.setMediaAspectRatio( + mediaAspectRatio: NativeMediaAspectRatio, +): NativeAdOptions.Builder { + return setMediaAspectRatio(mediaAspectRatio.value) +} + +fun defaultAdLoaderConfig( + adId: String, +) = AdLoaderConfig.Builder() + .adId(adId) .build() diff --git a/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAd.kt b/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAd.kt index eac630fe..a475bff8 100644 --- a/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAd.kt +++ b/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAd.kt @@ -33,9 +33,9 @@ import dev.teogor.ceres.monetisation.admob.formats.AdEvent fun NativeAd( modifier: Modifier = Modifier, nativeAdConfig: NativeAdConfig, - adContent: @Composable () -> Unit, + adContent: @Composable (NativeAdConfig) -> Unit, nativeAd: NativeAd?, - config: AdLoaderConfig = AdLoaderConfig(""), + config: AdLoaderConfig, refreshIntervalMillis: Long = 30000L, onAdEvent: (AdEvent) -> Unit = {}, onAdLoaded: (NativeAd) -> Unit = {}, @@ -76,7 +76,9 @@ fun NativeAd( adview.adChoicesView } - composeView.setContent(adContent) + composeView.setContent { + adContent(nativeAdConfig) + } } } diff --git a/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdUi.kt b/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdUi.kt deleted file mode 100644 index 68e43606..00000000 --- a/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdUi.kt +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright 2023 teogor (Teodor Grigor) - * - * 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 dev.teogor.ceres.monetisation.admob.formats.nativead - -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.ButtonDefaults -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp -import androidx.compose.ui.viewinterop.AndroidView -import dev.teogor.ceres.ui.theme.MaterialTheme - -@Composable -fun NativeAdUi( - nativeAdConfig: NativeAdConfig, -) { - val advertiserView = nativeAdConfig.advertiserView - val adChoicesView = nativeAdConfig.adChoicesView - val headlineView = nativeAdConfig.headlineView - val bodyView = nativeAdConfig.bodyView - val callToActionView = nativeAdConfig.callToActionView - val iconView = nativeAdConfig.iconView - val imageView = nativeAdConfig.imageView - val mediaView = nativeAdConfig.mediaView - val priceView = nativeAdConfig.priceView - val starRatingView = nativeAdConfig.starRatingView - val storeView = nativeAdConfig.storeView - - Column( - modifier = Modifier.fillMaxWidth(), - ) { - Row( - verticalAlignment = Alignment.CenterVertically, - ) { - iconView?.let { iconView -> - AndroidView( - factory = { - iconView.composeView - }, - ) - } - Column( - modifier = Modifier.padding(start = 6.dp), - ) { - headlineView?.let { headlineView -> - AndroidView( - factory = { - headlineView.composeView - }, - ) - } - bodyView?.let { bodyView -> - AndroidView( - factory = { - bodyView.composeView - }, - ) - } - } - } - callToActionView?.let { callToActionView -> - AndroidView( - factory = { - callToActionView.composeView - }, - modifier = Modifier - .align(Alignment.CenterHorizontally) - .background( - color = MaterialTheme.colorScheme.primary, - shape = ButtonDefaults.shape, - ) - .padding(horizontal = 10.dp, vertical = 6.dp), - ) - } - } -} diff --git a/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/models/AdChoicesPlacement.kt b/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/models/AdChoicesPlacement.kt new file mode 100644 index 00000000..9eb9d256 --- /dev/null +++ b/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/models/AdChoicesPlacement.kt @@ -0,0 +1,24 @@ +/* + * Copyright 2023 teogor (Teodor Grigor) + * + * 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 dev.teogor.ceres.monetisation.admob.models + +enum class AdChoicesPlacement(val value: Int) { + TOP_LEFT(0), + TOP_RIGHT(1), + BOTTOM_RIGHT(2), + BOTTOM_LEFT(3), +} diff --git a/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/models/NativeMediaAspectRatio.kt b/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/models/NativeMediaAspectRatio.kt new file mode 100644 index 00000000..cddf281f --- /dev/null +++ b/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/models/NativeMediaAspectRatio.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2023 teogor (Teodor Grigor) + * + * 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 dev.teogor.ceres.monetisation.admob.models + +enum class NativeMediaAspectRatio(val value: Int) { + UNKNOWN(0), + ANY(1), + LANDSCAPE(2), + PORTRAIT(3), + SQUARE(4), +} diff --git a/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/models/SwipeGestureDirection.kt b/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/models/SwipeGestureDirection.kt new file mode 100644 index 00000000..077ea733 --- /dev/null +++ b/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/models/SwipeGestureDirection.kt @@ -0,0 +1,24 @@ +/* + * Copyright 2023 teogor (Teodor Grigor) + * + * 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 dev.teogor.ceres.monetisation.admob.models + +enum class SwipeGestureDirection(val value: Int) { + RIGHT(1), + LEFT(2), + UP(4), + DOWN(8), +} From a3687bc50c55ede4fe7962d9363a53ce1916a510 Mon Sep 17 00:00:00 2001 From: Teodor Grigor Date: Fri, 27 Oct 2023 15:32:38 +0300 Subject: [PATCH 04/10] Added Progress Bar for Ad Loading --- .../dev/teogor/ceres/ads/HomeNativeAd.kt | 54 +++++++++++++------ monetisation/admob/api/admob.api | 20 +++---- .../admob/formats/nativead/NativeAd.kt | 11 +++- 3 files changed, 60 insertions(+), 25 deletions(-) diff --git a/app/src/main/kotlin/dev/teogor/ceres/ads/HomeNativeAd.kt b/app/src/main/kotlin/dev/teogor/ceres/ads/HomeNativeAd.kt index 9e2d7687..10911f45 100644 --- a/app/src/main/kotlin/dev/teogor/ceres/ads/HomeNativeAd.kt +++ b/app/src/main/kotlin/dev/teogor/ceres/ads/HomeNativeAd.kt @@ -26,13 +26,17 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.LinearProgressIndicator import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState import androidx.compose.runtime.derivedStateOf 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.draw.alpha import androidx.compose.ui.draw.clip import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.unit.dp @@ -60,6 +64,7 @@ import dev.teogor.ceres.monetisation.admob.formats.nativead.createStarRatingView import dev.teogor.ceres.monetisation.admob.formats.nativead.defaultAdLoaderConfig import dev.teogor.ceres.ui.designsystem.RatingBar import dev.teogor.ceres.ui.designsystem.Text +import dev.teogor.ceres.ui.designsystem.core.ColorUtils.blend import dev.teogor.ceres.ui.theme.MaterialTheme import javax.inject.Inject import javax.inject.Singleton @@ -86,19 +91,21 @@ fun HomeNativeAd( val adId = DemoAdUnitIds.NATIVE val nativeAd by remember { homeNativeAdVM.nativeAd } if (!isOffline) { + var isAdFillEmpty by remember { mutableStateOf(true) } NativeAd( modifier = Modifier.background( color = MaterialTheme.colorScheme.primaryContainer, shape = RoundedCornerShape(20.dp), ), nativeAdConfig = homeNativeAdConfig(), - adContent = { HomeNativeAdUI(it) }, + adContent = { HomeNativeAdUI(it, isAdFillEmpty) }, nativeAd = nativeAd, config = defaultAdLoaderConfig(adId), refreshIntervalMillis = 30_000L, onAdLoaded = { homeNativeAdVM.setNativeAd(it) }, + onAdFillStatusChange = { isAdFillEmpty = it }, ) } } @@ -175,6 +182,7 @@ fun homeNativeAdConfig() = NativeAdConfig.Builder() @Composable fun HomeNativeAdUI( nativeAdConfig: NativeAdConfig, + isAdFillEmpty: Boolean, ) { val advertiserView = nativeAdConfig.advertiserView val adChoicesView = nativeAdConfig.adChoicesView @@ -191,6 +199,7 @@ fun HomeNativeAdUI( Box { Column( modifier = Modifier + .alpha(if (isAdFillEmpty) 0f else 1f) .fillMaxWidth() .padding(vertical = 10.dp, horizontal = 6.dp), ) { @@ -241,19 +250,34 @@ fun HomeNativeAdUI( } } - Text( - text = "AD", - fontSize = 10.sp, - lineHeight = 10.sp, - color = MaterialTheme.colorScheme.onSecondaryContainer, - modifier = Modifier - .align(Alignment.BottomEnd) - .background( - color = MaterialTheme.colorScheme.secondaryContainer, - shape = CircleShape, - ) - .padding(horizontal = 8.dp, vertical = 8.dp) - .padding(end = 2.dp, bottom = 2.dp), - ) + if (isAdFillEmpty) { + LinearProgressIndicator( + modifier = Modifier + .align(Alignment.Center) + .padding(horizontal = 20.dp) + .fillMaxWidth() + .clip(RoundedCornerShape(50)), + color = MaterialTheme.colorScheme.primary, + trackColor = MaterialTheme.colorScheme.onPrimaryContainer.blend( + MaterialTheme.colorScheme.background, + fraction = .6f, + ), + ) + } else { + Text( + text = "AD", + fontSize = 10.sp, + lineHeight = 10.sp, + color = MaterialTheme.colorScheme.onSecondaryContainer, + modifier = Modifier + .align(Alignment.BottomEnd) + .background( + color = MaterialTheme.colorScheme.secondaryContainer, + shape = CircleShape, + ) + .padding(horizontal = 8.dp, vertical = 8.dp) + .padding(end = 2.dp, bottom = 2.dp), + ) + } } } diff --git a/monetisation/admob/api/admob.api b/monetisation/admob/api/admob.api index 19ac84ae..c72b748e 100644 --- a/monetisation/admob/api/admob.api +++ b/monetisation/admob/api/admob.api @@ -396,9 +396,9 @@ public final class dev/teogor/ceres/monetisation/admob/formats/nativead/AdCompon public final class dev/teogor/ceres/monetisation/admob/formats/nativead/AdLoaderConfig { public static final field $stable I - public final fun getAdChoicesPlacement ()I + public final fun getAdChoicesPlacement ()Ldev/teogor/ceres/monetisation/admob/models/AdChoicesPlacement; public final fun getAdId ()Ljava/lang/String; - public final fun getMediaAspectRatio ()I + public final fun getMediaAspectRatio ()Ldev/teogor/ceres/monetisation/admob/models/NativeMediaAspectRatio; public final fun getRequestCustomMuteThisAd ()Z public final fun getRequestMultipleImages ()Z public final fun getReturnUrlsForImageAssets ()Z @@ -408,18 +408,18 @@ public final class dev/teogor/ceres/monetisation/admob/formats/nativead/AdLoader public final class dev/teogor/ceres/monetisation/admob/formats/nativead/AdLoaderConfig$Builder { public static final field $stable I public fun ()V - public fun (Ljava/lang/String;IIZZZZZZ)V - public synthetic fun (Ljava/lang/String;IIZZZZZZILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun adChoicesPlacement (I)Ldev/teogor/ceres/monetisation/admob/formats/nativead/AdLoaderConfig$Builder; + public fun (Ljava/lang/String;Ldev/teogor/ceres/monetisation/admob/models/AdChoicesPlacement;Ldev/teogor/ceres/monetisation/admob/models/NativeMediaAspectRatio;ZZZZZZ)V + public synthetic fun (Ljava/lang/String;Ldev/teogor/ceres/monetisation/admob/models/AdChoicesPlacement;Ldev/teogor/ceres/monetisation/admob/models/NativeMediaAspectRatio;ZZZZZZILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun adChoicesPlacement (Ldev/teogor/ceres/monetisation/admob/models/AdChoicesPlacement;)Ldev/teogor/ceres/monetisation/admob/formats/nativead/AdLoaderConfig$Builder; public final fun adId (Ljava/lang/String;)Ldev/teogor/ceres/monetisation/admob/formats/nativead/AdLoaderConfig$Builder; public final fun build ()Ldev/teogor/ceres/monetisation/admob/formats/nativead/AdLoaderConfig; public final fun clickToExpandRequested (Z)Ldev/teogor/ceres/monetisation/admob/formats/nativead/AdLoaderConfig$Builder; - public final fun copy (Ljava/lang/String;IIZZZZZZ)Ldev/teogor/ceres/monetisation/admob/formats/nativead/AdLoaderConfig$Builder; - public static synthetic fun copy$default (Ldev/teogor/ceres/monetisation/admob/formats/nativead/AdLoaderConfig$Builder;Ljava/lang/String;IIZZZZZZILjava/lang/Object;)Ldev/teogor/ceres/monetisation/admob/formats/nativead/AdLoaderConfig$Builder; + public final fun copy (Ljava/lang/String;Ldev/teogor/ceres/monetisation/admob/models/AdChoicesPlacement;Ldev/teogor/ceres/monetisation/admob/models/NativeMediaAspectRatio;ZZZZZZ)Ldev/teogor/ceres/monetisation/admob/formats/nativead/AdLoaderConfig$Builder; + public static synthetic fun copy$default (Ldev/teogor/ceres/monetisation/admob/formats/nativead/AdLoaderConfig$Builder;Ljava/lang/String;Ldev/teogor/ceres/monetisation/admob/models/AdChoicesPlacement;Ldev/teogor/ceres/monetisation/admob/models/NativeMediaAspectRatio;ZZZZZZILjava/lang/Object;)Ldev/teogor/ceres/monetisation/admob/formats/nativead/AdLoaderConfig$Builder; public final fun customControlsRequested (Z)Ldev/teogor/ceres/monetisation/admob/formats/nativead/AdLoaderConfig$Builder; public fun equals (Ljava/lang/Object;)Z public fun hashCode ()I - public final fun mediaAspectRatio (I)Ldev/teogor/ceres/monetisation/admob/formats/nativead/AdLoaderConfig$Builder; + public final fun mediaAspectRatio (Ldev/teogor/ceres/monetisation/admob/models/NativeMediaAspectRatio;)Ldev/teogor/ceres/monetisation/admob/formats/nativead/AdLoaderConfig$Builder; public final fun requestCustomMuteThisAd (Z)Ldev/teogor/ceres/monetisation/admob/formats/nativead/AdLoaderConfig$Builder; public final fun requestMultipleImages (Z)Ldev/teogor/ceres/monetisation/admob/formats/nativead/AdLoaderConfig$Builder; public final fun returnUrlsForImageAssets (Z)Ldev/teogor/ceres/monetisation/admob/formats/nativead/AdLoaderConfig$Builder; @@ -429,6 +429,8 @@ public final class dev/teogor/ceres/monetisation/admob/formats/nativead/AdLoader public final class dev/teogor/ceres/monetisation/admob/formats/nativead/AdLoaderConfigKt { public static final fun defaultAdLoaderConfig (Ljava/lang/String;)Ldev/teogor/ceres/monetisation/admob/formats/nativead/AdLoaderConfig; + public static final fun setAdChoicesPlacement (Lcom/google/android/gms/ads/nativead/NativeAdOptions$Builder;Ldev/teogor/ceres/monetisation/admob/models/AdChoicesPlacement;)Lcom/google/android/gms/ads/nativead/NativeAdOptions$Builder; + public static final fun setMediaAspectRatio (Lcom/google/android/gms/ads/nativead/NativeAdOptions$Builder;Ldev/teogor/ceres/monetisation/admob/models/NativeMediaAspectRatio;)Lcom/google/android/gms/ads/nativead/NativeAdOptions$Builder; public static final fun toNativeAdOptions (Ldev/teogor/ceres/monetisation/admob/formats/nativead/AdLoaderConfig;)Lcom/google/android/gms/ads/nativead/NativeAdOptions; } @@ -538,7 +540,7 @@ public final class dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAd } public final class dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdKt { - public static final fun NativeAd (Landroidx/compose/ui/Modifier;Ldev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdConfig;Lkotlin/jvm/functions/Function3;Lcom/google/android/gms/ads/nativead/NativeAd;Ldev/teogor/ceres/monetisation/admob/formats/nativead/AdLoaderConfig;JLkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V + public static final fun NativeAd (Landroidx/compose/ui/Modifier;Ldev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdConfig;Lkotlin/jvm/functions/Function3;Lcom/google/android/gms/ads/nativead/NativeAd;Ldev/teogor/ceres/monetisation/admob/formats/nativead/AdLoaderConfig;JLkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V } public abstract class dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdViewModel : androidx/lifecycle/ViewModel { diff --git a/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAd.kt b/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAd.kt index a475bff8..42c9af16 100644 --- a/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAd.kt +++ b/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAd.kt @@ -21,6 +21,7 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.viewinterop.AndroidViewBinding @@ -39,10 +40,15 @@ fun NativeAd( refreshIntervalMillis: Long = 30000L, onAdEvent: (AdEvent) -> Unit = {}, onAdLoaded: (NativeAd) -> Unit = {}, + onAdFillStatusChange: (Boolean) -> Unit = {}, ) { var adView by remember { mutableStateOf(null) } + var isAdFillEmpty by rememberSaveable { mutableStateOf(true) } + LaunchedEffect(isAdFillEmpty) { + onAdFillStatusChange(isAdFillEmpty) + } val adLoader = rememberAdLoader( config = config, @@ -83,7 +89,10 @@ fun NativeAd( } LaunchedEffect(nativeAd) { - nativeAd?.let { nativeAd -> + if (nativeAd == null) { + isAdFillEmpty = true + } else { + isAdFillEmpty = false nativeAd.body?.let { body -> nativeAdConfig.bodyView?.setValue(body) } From f491dc1d8b0131583e123c20e5e1abf0879c47f9 Mon Sep 17 00:00:00 2001 From: Teodor Grigor Date: Fri, 27 Oct 2023 15:48:01 +0300 Subject: [PATCH 05/10] Enhanced Native Ad Management for Improved Usage --- .../dev/teogor/ceres/ads/HomeNativeAd.kt | 14 +++----- monetisation/admob/api/admob.api | 2 +- .../admob/formats/nativead/NativeAd.kt | 35 ++++++++++--------- 3 files changed, 24 insertions(+), 27 deletions(-) diff --git a/app/src/main/kotlin/dev/teogor/ceres/ads/HomeNativeAd.kt b/app/src/main/kotlin/dev/teogor/ceres/ads/HomeNativeAd.kt index 10911f45..935dc3df 100644 --- a/app/src/main/kotlin/dev/teogor/ceres/ads/HomeNativeAd.kt +++ b/app/src/main/kotlin/dev/teogor/ceres/ads/HomeNativeAd.kt @@ -39,6 +39,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.clip import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.viewinterop.AndroidView @@ -62,6 +63,7 @@ import dev.teogor.ceres.monetisation.admob.formats.nativead.createHeadlineView import dev.teogor.ceres.monetisation.admob.formats.nativead.createIconView import dev.teogor.ceres.monetisation.admob.formats.nativead.createStarRatingView import dev.teogor.ceres.monetisation.admob.formats.nativead.defaultAdLoaderConfig +import dev.teogor.ceres.monetisation.admob.models.AdChoicesPlacement import dev.teogor.ceres.ui.designsystem.RatingBar import dev.teogor.ceres.ui.designsystem.Text import dev.teogor.ceres.ui.designsystem.core.ColorUtils.blend @@ -81,30 +83,24 @@ class HomeNativeAdVM @Inject constructor( ) : NativeAdViewModel(homeNativeAdData) @Composable -fun HomeNativeAd( - homeNativeAdVM: HomeNativeAdVM = hiltViewModel(), -) { +fun HomeNativeAd() { val networkConnectivity = LocalNetworkConnectivity.current val isOffline by remember { derivedStateOf { networkConnectivity.isOffline } } val adId = DemoAdUnitIds.NATIVE - val nativeAd by remember { homeNativeAdVM.nativeAd } if (!isOffline) { var isAdFillEmpty by remember { mutableStateOf(true) } - NativeAd( + NativeAd( + viewModel = hiltViewModel(), modifier = Modifier.background( color = MaterialTheme.colorScheme.primaryContainer, shape = RoundedCornerShape(20.dp), ), nativeAdConfig = homeNativeAdConfig(), adContent = { HomeNativeAdUI(it, isAdFillEmpty) }, - nativeAd = nativeAd, config = defaultAdLoaderConfig(adId), refreshIntervalMillis = 30_000L, - onAdLoaded = { - homeNativeAdVM.setNativeAd(it) - }, onAdFillStatusChange = { isAdFillEmpty = it }, ) } diff --git a/monetisation/admob/api/admob.api b/monetisation/admob/api/admob.api index c72b748e..2b58e7e3 100644 --- a/monetisation/admob/api/admob.api +++ b/monetisation/admob/api/admob.api @@ -540,7 +540,7 @@ public final class dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAd } public final class dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdKt { - public static final fun NativeAd (Landroidx/compose/ui/Modifier;Ldev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdConfig;Lkotlin/jvm/functions/Function3;Lcom/google/android/gms/ads/nativead/NativeAd;Ldev/teogor/ceres/monetisation/admob/formats/nativead/AdLoaderConfig;JLkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V + public static final fun NativeAd (Ldev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdViewModel;Landroidx/compose/ui/Modifier;Ldev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdConfig;Lkotlin/jvm/functions/Function3;Ldev/teogor/ceres/monetisation/admob/formats/nativead/AdLoaderConfig;JLkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V } public abstract class dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdViewModel : androidx/lifecycle/ViewModel { diff --git a/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAd.kt b/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAd.kt index 42c9af16..b212c2a2 100644 --- a/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAd.kt +++ b/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAd.kt @@ -25,23 +25,22 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.viewinterop.AndroidViewBinding -import com.google.android.gms.ads.nativead.NativeAd import com.google.android.gms.ads.nativead.NativeAdView import dev.teogor.ceres.monetisation.admob.databinding.AdmobNativeBinding import dev.teogor.ceres.monetisation.admob.formats.AdEvent @Composable -fun NativeAd( +fun NativeAd( + viewModel: T, modifier: Modifier = Modifier, nativeAdConfig: NativeAdConfig, adContent: @Composable (NativeAdConfig) -> Unit, - nativeAd: NativeAd?, config: AdLoaderConfig, refreshIntervalMillis: Long = 30000L, onAdEvent: (AdEvent) -> Unit = {}, - onAdLoaded: (NativeAd) -> Unit = {}, onAdFillStatusChange: (Boolean) -> Unit = {}, ) { + val nativeAd by remember { viewModel.nativeAd } var adView by remember { mutableStateOf(null) } @@ -53,7 +52,9 @@ fun NativeAd( val adLoader = rememberAdLoader( config = config, onAdEvent = onAdEvent, - onNativeAd = onAdLoaded, + onNativeAd = { + viewModel.setNativeAd(it) + }, ) RefreshableNativeAd( @@ -93,40 +94,40 @@ fun NativeAd( isAdFillEmpty = true } else { isAdFillEmpty = false - nativeAd.body?.let { body -> + nativeAd!!.body?.let { body -> nativeAdConfig.bodyView?.setValue(body) } - nativeAd.advertiser?.let { advertiser -> + nativeAd!!.advertiser?.let { advertiser -> nativeAdConfig.advertiserView?.setValue(advertiser) } - nativeAd.adChoicesInfo?.let { adChoices -> + nativeAd!!.adChoicesInfo?.let { adChoices -> nativeAdConfig.adChoicesView?.setValue(adChoices) } - nativeAd.headline?.let { headline -> + nativeAd!!.headline?.let { headline -> nativeAdConfig.headlineView?.setValue(headline) } - nativeAd.callToAction?.let { callToAction -> + nativeAd!!.callToAction?.let { callToAction -> nativeAdConfig.callToActionView?.setValue(callToAction) } - nativeAd.icon?.let { icon -> + nativeAd!!.icon?.let { icon -> nativeAdConfig.iconView?.setValue(icon) } - nativeAd.images.let { image -> + nativeAd!!.images.let { image -> nativeAdConfig.imageView?.setValue(image) } - nativeAd.mediaContent?.let { media -> + nativeAd!!.mediaContent?.let { media -> nativeAdConfig.mediaView?.setValue(media) } - nativeAd.price?.let { price -> + nativeAd!!.price?.let { price -> nativeAdConfig.priceView?.setValue(price) } - nativeAd.starRating?.let { starRating -> + nativeAd!!.starRating?.let { starRating -> nativeAdConfig.starRatingView?.setValue(starRating) } - nativeAd.store?.let { store -> + nativeAd!!.store?.let { store -> nativeAdConfig.storeView?.setValue(store) } - adView?.setNativeAd(nativeAd) + adView?.setNativeAd(nativeAd!!) } } } From 95c6b3020756ab95224070282bd61be11bd7b750 Mon Sep 17 00:00:00 2001 From: Teodor Grigor Date: Fri, 27 Oct 2023 16:20:36 +0300 Subject: [PATCH 06/10] Added Composable Function for Native Ad Background and Corner Shape --- .../dev/teogor/ceres/ads/HomeNativeAd.kt | 27 +++-- .../teogor/ceres/feature/home/HomeScreen.kt | 18 +-- monetisation/admob/api/admob.api | 8 +- .../admob/formats/nativead/NativeAd.kt | 5 +- .../formats/nativead/NativeAdModifiers.kt | 103 ++++++++++++++++++ 5 files changed, 133 insertions(+), 28 deletions(-) create mode 100644 monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdModifiers.kt diff --git a/app/src/main/kotlin/dev/teogor/ceres/ads/HomeNativeAd.kt b/app/src/main/kotlin/dev/teogor/ceres/ads/HomeNativeAd.kt index 935dc3df..9c25942e 100644 --- a/app/src/main/kotlin/dev/teogor/ceres/ads/HomeNativeAd.kt +++ b/app/src/main/kotlin/dev/teogor/ceres/ads/HomeNativeAd.kt @@ -39,7 +39,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.clip import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.viewinterop.AndroidView @@ -63,7 +62,7 @@ import dev.teogor.ceres.monetisation.admob.formats.nativead.createHeadlineView import dev.teogor.ceres.monetisation.admob.formats.nativead.createIconView import dev.teogor.ceres.monetisation.admob.formats.nativead.createStarRatingView import dev.teogor.ceres.monetisation.admob.formats.nativead.defaultAdLoaderConfig -import dev.teogor.ceres.monetisation.admob.models.AdChoicesPlacement +import dev.teogor.ceres.monetisation.admob.formats.nativead.nativeAdBackground import dev.teogor.ceres.ui.designsystem.RatingBar import dev.teogor.ceres.ui.designsystem.Text import dev.teogor.ceres.ui.designsystem.core.ColorUtils.blend @@ -92,16 +91,26 @@ fun HomeNativeAd() { if (!isOffline) { var isAdFillEmpty by remember { mutableStateOf(true) } NativeAd( + modifier = Modifier + .padding( + horizontal = 10.dp, + vertical = 10.dp, + ) + .fillMaxWidth(), viewModel = hiltViewModel(), - modifier = Modifier.background( - color = MaterialTheme.colorScheme.primaryContainer, - shape = RoundedCornerShape(20.dp), - ), nativeAdConfig = homeNativeAdConfig(), adContent = { HomeNativeAdUI(it, isAdFillEmpty) }, config = defaultAdLoaderConfig(adId), refreshIntervalMillis = 30_000L, onAdFillStatusChange = { isAdFillEmpty = it }, + onRetrieveBackground = { + nativeAdBackground( + color = MaterialTheme.colorScheme.tertiaryContainer, + adChoicesPlacement = it, + cornerSize = 20.dp, + shadowElevation = 2.dp, + ) + }, ) } } @@ -254,7 +263,7 @@ fun HomeNativeAdUI( .fillMaxWidth() .clip(RoundedCornerShape(50)), color = MaterialTheme.colorScheme.primary, - trackColor = MaterialTheme.colorScheme.onPrimaryContainer.blend( + trackColor = MaterialTheme.colorScheme.onTertiaryContainer.blend( MaterialTheme.colorScheme.background, fraction = .6f, ), @@ -264,11 +273,11 @@ fun HomeNativeAdUI( text = "AD", fontSize = 10.sp, lineHeight = 10.sp, - color = MaterialTheme.colorScheme.onSecondaryContainer, + color = MaterialTheme.colorScheme.onTertiary, modifier = Modifier .align(Alignment.BottomEnd) .background( - color = MaterialTheme.colorScheme.secondaryContainer, + color = MaterialTheme.colorScheme.tertiary, shape = CircleShape, ) .padding(horizontal = 8.dp, vertical = 8.dp) diff --git a/app/src/main/kotlin/dev/teogor/ceres/feature/home/HomeScreen.kt b/app/src/main/kotlin/dev/teogor/ceres/feature/home/HomeScreen.kt index b0154abc..98334ea4 100644 --- a/app/src/main/kotlin/dev/teogor/ceres/feature/home/HomeScreen.kt +++ b/app/src/main/kotlin/dev/teogor/ceres/feature/home/HomeScreen.kt @@ -16,13 +16,8 @@ package dev.teogor.ceres.feature.home -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import dev.teogor.ceres.R import dev.teogor.ceres.ads.HomeNativeAd @@ -47,7 +42,6 @@ import dev.teogor.ceres.screen.builder.compose.SimpleView import dev.teogor.ceres.screen.core.layout.ColumnLayoutBase import dev.teogor.ceres.screen.ui.api.ExperimentalOnboardingScreenApi import dev.teogor.ceres.screen.ui.onboarding.OnboardingRoute -import dev.teogor.ceres.ui.designsystem.Surface // todo better way to configure this. perhaps use kotlin builder syntax @Composable @@ -157,15 +151,5 @@ private fun HomeScreen( }, ) - Surface( - modifier = Modifier - .padding(horizontal = 10.dp) - .padding(vertical = 10.dp) - .fillMaxWidth(), - shape = RoundedCornerShape(20.dp), - tonalElevation = 6.dp, - shadowElevation = 4.dp, - ) { - HomeNativeAd() - } + HomeNativeAd() } diff --git a/monetisation/admob/api/admob.api b/monetisation/admob/api/admob.api index 2b58e7e3..6d746dbf 100644 --- a/monetisation/admob/api/admob.api +++ b/monetisation/admob/api/admob.api @@ -540,7 +540,13 @@ public final class dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAd } public final class dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdKt { - public static final fun NativeAd (Ldev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdViewModel;Landroidx/compose/ui/Modifier;Ldev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdConfig;Lkotlin/jvm/functions/Function3;Ldev/teogor/ceres/monetisation/admob/formats/nativead/AdLoaderConfig;JLkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V + public static final fun NativeAd (Ldev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdViewModel;Landroidx/compose/ui/Modifier;Ldev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdConfig;Lkotlin/jvm/functions/Function3;Ldev/teogor/ceres/monetisation/admob/formats/nativead/AdLoaderConfig;JLkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;II)V +} + +public final class dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdModifiersKt { + public static final fun nativeAdBackground-36lltbM (JFFLdev/teogor/ceres/monetisation/admob/models/AdChoicesPlacement;Landroidx/compose/runtime/Composer;II)Landroidx/compose/ui/Modifier; + public static final fun nativeAdRoundedCornerShape-3ABfNKs (Ldev/teogor/ceres/monetisation/admob/models/AdChoicesPlacement;F)Landroidx/compose/foundation/shape/RoundedCornerShape; + public static synthetic fun nativeAdRoundedCornerShape-3ABfNKs$default (Ldev/teogor/ceres/monetisation/admob/models/AdChoicesPlacement;FILjava/lang/Object;)Landroidx/compose/foundation/shape/RoundedCornerShape; } public abstract class dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdViewModel : androidx/lifecycle/ViewModel { diff --git a/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAd.kt b/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAd.kt index b212c2a2..8ca25403 100644 --- a/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAd.kt +++ b/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAd.kt @@ -28,6 +28,7 @@ import androidx.compose.ui.viewinterop.AndroidViewBinding import com.google.android.gms.ads.nativead.NativeAdView import dev.teogor.ceres.monetisation.admob.databinding.AdmobNativeBinding import dev.teogor.ceres.monetisation.admob.formats.AdEvent +import dev.teogor.ceres.monetisation.admob.models.AdChoicesPlacement @Composable fun NativeAd( @@ -39,6 +40,7 @@ fun NativeAd( refreshIntervalMillis: Long = 30000L, onAdEvent: (AdEvent) -> Unit = {}, onAdFillStatusChange: (Boolean) -> Unit = {}, + onRetrieveBackground: @Composable (AdChoicesPlacement) -> Modifier = { Modifier }, ) { val nativeAd by remember { viewModel.nativeAd } var adView by remember { @@ -63,9 +65,10 @@ fun NativeAd( refreshIntervalMillis = refreshIntervalMillis, ) + val backgroundModifier = onRetrieveBackground(config.adChoicesPlacement) AndroidViewBinding( factory = AdmobNativeBinding::inflate, - modifier = modifier, + modifier = modifier.then(backgroundModifier), ) { if (adView == null) { adView = root.also { adview -> diff --git a/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdModifiers.kt b/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdModifiers.kt new file mode 100644 index 00000000..9168e38b --- /dev/null +++ b/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdModifiers.kt @@ -0,0 +1,103 @@ +/* + * Copyright 2023 teogor (Teodor Grigor) + * + * 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 dev.teogor.ceres.monetisation.admob.formats.nativead + +import android.annotation.SuppressLint +import androidx.compose.foundation.background +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import dev.teogor.ceres.monetisation.admob.models.AdChoicesPlacement + +/** + * Composable function that creates a background [Modifier] for a native + * ad, with options for customizing color, corner size, shadow elevation, + * and AdChoices placement. + * + * @param color The background color of the native ad. + * @param cornerSize The corner size for the rounded corners. Default is 0.dp. + * @param shadowElevation The shadow elevation for the background. Default is 0.dp. + * @param adChoicesPlacement The placement of AdChoices in the native ad. + * @return A [Modifier] that represents the background with specified properties. + */ +@SuppressLint("ModifierFactoryExtensionFunction") +@Composable +fun nativeAdBackground( + color: Color, + cornerSize: Dp = 0.dp, + shadowElevation: Dp = 0.dp, + adChoicesPlacement: AdChoicesPlacement, +): Modifier { + val shape = adChoicesPlacement.nativeAdRoundedCornerShape(cornerSize) + return Modifier + .shadow( + elevation = shadowElevation, + shape = shape, + clip = true, + ) + .background( + color = color, + shape = shape, + ) + .clip(shape) +} + +/** + * Creates a [RoundedCornerShape] based on the provided [AdChoicesPlacement] + * and corner size. + * + * @param cornerSize The corner size for the rounded corners. + * @return A [RoundedCornerShape] that has rounded corners based on the [AdChoicesPlacement]. + */ +fun AdChoicesPlacement.nativeAdRoundedCornerShape( + cornerSize: Dp = 0.dp, +): RoundedCornerShape { + return when (this) { + AdChoicesPlacement.TOP_LEFT -> RoundedCornerShape( + 0.dp, + cornerSize, + cornerSize, + cornerSize, + ) + + AdChoicesPlacement.TOP_RIGHT -> RoundedCornerShape( + cornerSize, + 0.dp, + cornerSize, + cornerSize, + ) + + AdChoicesPlacement.BOTTOM_RIGHT -> RoundedCornerShape( + cornerSize, + cornerSize, + 0.dp, + cornerSize, + ) + + AdChoicesPlacement.BOTTOM_LEFT -> RoundedCornerShape( + cornerSize, + cornerSize, + cornerSize, + 0.dp, + ) + } +} From bb9ae8156b18f13306eb9c91f181b5e687c663a4 Mon Sep 17 00:00:00 2001 From: Teodor Grigor Date: Fri, 27 Oct 2023 16:31:58 +0300 Subject: [PATCH 07/10] Added RenderContent() Extension Function for Simplifying Rendering --- .../dev/teogor/ceres/ads/HomeNativeAd.kt | 48 ++++--------------- monetisation/admob/api/admob.api | 1 + .../formats/nativead/NativeAdComponent.kt | 20 ++++++++ 3 files changed, 31 insertions(+), 38 deletions(-) diff --git a/app/src/main/kotlin/dev/teogor/ceres/ads/HomeNativeAd.kt b/app/src/main/kotlin/dev/teogor/ceres/ads/HomeNativeAd.kt index 9c25942e..ff75809c 100644 --- a/app/src/main/kotlin/dev/teogor/ceres/ads/HomeNativeAd.kt +++ b/app/src/main/kotlin/dev/teogor/ceres/ads/HomeNativeAd.kt @@ -41,7 +41,6 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.compose.ui.viewinterop.AndroidView import androidx.hilt.navigation.compose.hiltViewModel import com.google.android.gms.ads.nativead.NativeAd import com.skydoves.landscapist.ImageOptions @@ -56,6 +55,7 @@ import dev.teogor.ceres.monetisation.admob.formats.nativead.NativeAd import dev.teogor.ceres.monetisation.admob.formats.nativead.NativeAdConfig import dev.teogor.ceres.monetisation.admob.formats.nativead.NativeAdData import dev.teogor.ceres.monetisation.admob.formats.nativead.NativeAdViewModel +import dev.teogor.ceres.monetisation.admob.formats.nativead.RenderContent import dev.teogor.ceres.monetisation.admob.formats.nativead.createBodyView import dev.teogor.ceres.monetisation.admob.formats.nativead.createCallToActionView import dev.teogor.ceres.monetisation.admob.formats.nativead.createHeadlineView @@ -211,48 +211,20 @@ fun HomeNativeAdUI( Row( verticalAlignment = Alignment.CenterVertically, ) { - iconView?.let { iconView -> - AndroidView( - factory = { - iconView.composeView - }, - modifier = Modifier.align(Alignment.Top), - ) - } + iconView.RenderContent( + modifier = Modifier.align(Alignment.Top), + ) Column( modifier = Modifier.padding(start = 6.dp), ) { - headlineView?.let { headlineView -> - AndroidView( - factory = { - headlineView.composeView - }, - ) - } - starRatingView?.let { starRatingView -> - AndroidView( - factory = { - starRatingView.composeView - }, - ) - } - bodyView?.let { bodyView -> - AndroidView( - factory = { - bodyView.composeView - }, - ) - } + headlineView.RenderContent() + starRatingView.RenderContent() + bodyView.RenderContent() } } - callToActionView?.let { callToActionView -> - AndroidView( - factory = { - callToActionView.composeView - }, - modifier = Modifier.align(Alignment.CenterHorizontally), - ) - } + callToActionView.RenderContent( + modifier = Modifier.align(Alignment.CenterHorizontally), + ) } if (isAdFillEmpty) { diff --git a/monetisation/admob/api/admob.api b/monetisation/admob/api/admob.api index 6d746dbf..41faf0e3 100644 --- a/monetisation/admob/api/admob.api +++ b/monetisation/admob/api/admob.api @@ -455,6 +455,7 @@ public final class dev/teogor/ceres/monetisation/admob/formats/nativead/MediaCon } public final class dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdComponentKt { + public static final fun RenderContent (Ldev/teogor/ceres/monetisation/admob/formats/nativead/AdComponent;Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;II)V public static final fun createAdChoicesView (Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;I)Ldev/teogor/ceres/monetisation/admob/formats/nativead/AdComponent; public static final fun createAdComponent (Ljava/lang/Object;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;I)Ldev/teogor/ceres/monetisation/admob/formats/nativead/AdComponent; public static final fun createAdvertiserView (Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;I)Ldev/teogor/ceres/monetisation/admob/formats/nativead/AdComponent; diff --git a/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdComponent.kt b/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdComponent.kt index ae1513c7..e38d899e 100644 --- a/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdComponent.kt +++ b/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdComponent.kt @@ -20,8 +20,10 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.viewinterop.AndroidView import com.google.android.gms.ads.MediaContent import com.google.android.gms.ads.nativead.NativeAd @@ -34,6 +36,24 @@ data class AdComponent( } } +/** + * A composable extension function that simplifies rendering an + * [AdComponent] by wrapping it in an [AndroidView]. + * + * @param modifier The modifier for the [AndroidView]. + */ +@Composable +fun AdComponent?.RenderContent( + modifier: Modifier = Modifier, +) { + this?.let { component -> + AndroidView( + factory = { component.composeView }, + modifier = modifier, + ) + } +} + @Composable fun createAdComponent( initialValue: T, From 3640a5db2b6b44745f90279aec41306cf2650edb Mon Sep 17 00:00:00 2001 From: Teodor Grigor Date: Fri, 27 Oct 2023 16:46:58 +0300 Subject: [PATCH 08/10] Added NativeAdContainer composable for Ad Display --- .../dev/teogor/ceres/ads/HomeNativeAd.kt | 70 ++++++--------- monetisation/admob/api/admob.api | 11 +++ .../formats/nativead/NativeAdContainer.kt | 86 +++++++++++++++++++ 3 files changed, 123 insertions(+), 44 deletions(-) create mode 100644 monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdContainer.kt diff --git a/app/src/main/kotlin/dev/teogor/ceres/ads/HomeNativeAd.kt b/app/src/main/kotlin/dev/teogor/ceres/ads/HomeNativeAd.kt index ff75809c..fb7a5b60 100644 --- a/app/src/main/kotlin/dev/teogor/ceres/ads/HomeNativeAd.kt +++ b/app/src/main/kotlin/dev/teogor/ceres/ads/HomeNativeAd.kt @@ -17,13 +17,11 @@ package dev.teogor.ceres.ads import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.LinearProgressIndicator @@ -36,7 +34,6 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.clip import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.unit.dp @@ -53,6 +50,7 @@ import dev.teogor.ceres.monetisation.admob.annotations.AdProperty import dev.teogor.ceres.monetisation.admob.annotations.AdUI import dev.teogor.ceres.monetisation.admob.formats.nativead.NativeAd import dev.teogor.ceres.monetisation.admob.formats.nativead.NativeAdConfig +import dev.teogor.ceres.monetisation.admob.formats.nativead.NativeAdContainer import dev.teogor.ceres.monetisation.admob.formats.nativead.NativeAdData import dev.teogor.ceres.monetisation.admob.formats.nativead.NativeAdViewModel import dev.teogor.ceres.monetisation.admob.formats.nativead.RenderContent @@ -201,33 +199,14 @@ fun HomeNativeAdUI( val starRatingView = nativeAdConfig.starRatingView val storeView = nativeAdConfig.storeView - Box { - Column( - modifier = Modifier - .alpha(if (isAdFillEmpty) 0f else 1f) - .fillMaxWidth() - .padding(vertical = 10.dp, horizontal = 6.dp), - ) { - Row( - verticalAlignment = Alignment.CenterVertically, - ) { - iconView.RenderContent( - modifier = Modifier.align(Alignment.Top), - ) - Column( - modifier = Modifier.padding(start = 6.dp), - ) { - headlineView.RenderContent() - starRatingView.RenderContent() - bodyView.RenderContent() - } - } - callToActionView.RenderContent( - modifier = Modifier.align(Alignment.CenterHorizontally), - ) - } - - if (isAdFillEmpty) { + NativeAdContainer( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 10.dp, horizontal = 6.dp), + isAdFillEmpty = isAdFillEmpty, + showDefaultAdText = true, + adTextAlignment = Alignment.BottomEnd, + loadingOverlay = { LinearProgressIndicator( modifier = Modifier .align(Alignment.Center) @@ -240,21 +219,24 @@ fun HomeNativeAdUI( fraction = .6f, ), ) - } else { - Text( - text = "AD", - fontSize = 10.sp, - lineHeight = 10.sp, - color = MaterialTheme.colorScheme.onTertiary, - modifier = Modifier - .align(Alignment.BottomEnd) - .background( - color = MaterialTheme.colorScheme.tertiary, - shape = CircleShape, - ) - .padding(horizontal = 8.dp, vertical = 8.dp) - .padding(end = 2.dp, bottom = 2.dp), + }, + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + ) { + iconView.RenderContent( + modifier = Modifier.align(Alignment.Top), ) + Column( + modifier = Modifier.padding(start = 6.dp), + ) { + headlineView.RenderContent() + starRatingView.RenderContent() + bodyView.RenderContent() + } } + callToActionView.RenderContent( + modifier = Modifier.align(Alignment.CenterHorizontally), + ) } } diff --git a/monetisation/admob/api/admob.api b/monetisation/admob/api/admob.api index 41faf0e3..c26e059c 100644 --- a/monetisation/admob/api/admob.api +++ b/monetisation/admob/api/admob.api @@ -438,6 +438,13 @@ public final class dev/teogor/ceres/monetisation/admob/formats/nativead/AdLoader public static final fun rememberAdLoader (Ldev/teogor/ceres/monetisation/admob/formats/nativead/AdLoaderConfig;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)Lcom/google/android/gms/ads/AdLoader; } +public final class dev/teogor/ceres/monetisation/admob/formats/nativead/ComposableSingletons$NativeAdContainerKt { + public static final field INSTANCE Ldev/teogor/ceres/monetisation/admob/formats/nativead/ComposableSingletons$NativeAdContainerKt; + public static field lambda-1 Lkotlin/jvm/functions/Function3; + public fun ()V + public final fun getLambda-1$admob_release ()Lkotlin/jvm/functions/Function3; +} + public final class dev/teogor/ceres/monetisation/admob/formats/nativead/MediaContentImpl : com/google/android/gms/ads/MediaContent { public static final field $stable I public fun ()V @@ -529,6 +536,10 @@ public final class dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAd public fun toString ()Ljava/lang/String; } +public final class dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdContainerKt { + public static final fun NativeAdContainer (Landroidx/compose/ui/Modifier;ZLkotlin/jvm/functions/Function3;ZLandroidx/compose/ui/Alignment;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;II)V +} + public abstract class dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdData { public static final field $stable I public fun (Landroidx/compose/runtime/MutableState;)V diff --git a/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdContainer.kt b/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdContainer.kt new file mode 100644 index 00000000..b0d6dadb --- /dev/null +++ b/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdContainer.kt @@ -0,0 +1,86 @@ +/* + * Copyright 2023 teogor (Teodor Grigor) + * + * 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 dev.teogor.ceres.monetisation.admob.formats.nativead + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxScope +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import dev.teogor.ceres.ui.designsystem.Text +import dev.teogor.ceres.ui.theme.MaterialTheme + +/** + * A composable container for displaying native ads with optional + * loading overlay and custom text. + * + * @param modifier The modifier to be applied to the container. + * @param isAdFillEmpty Flag indicating whether the ad content is empty. + * @param loadingOverlay Composable lambda for displaying a loading overlay. + * @param showDefaultAdText Flag to determine if the default "AD" text + * should be displayed. + * @param adTextAlignment Alignment for positioning the "AD" text within + * the container. + * @param content The content of the native ad. + */ +@Composable +fun NativeAdContainer( + modifier: Modifier = Modifier, + isAdFillEmpty: Boolean = false, + loadingOverlay: @Composable BoxScope.() -> Unit = {}, + showDefaultAdText: Boolean = false, + adTextAlignment: Alignment = Alignment.BottomEnd, + content: @Composable ColumnScope.() -> Unit, +) { + Box { + Column( + modifier = Modifier + .alpha(if (isAdFillEmpty) 0f else 1f) + .then(modifier), + content = content, + ) + + if (showDefaultAdText && !isAdFillEmpty) { + Text( + text = "AD", + fontSize = 10.sp, + lineHeight = 10.sp, + color = MaterialTheme.colorScheme.onTertiary, + modifier = Modifier + .align(adTextAlignment) + .background( + color = MaterialTheme.colorScheme.tertiary, + shape = CircleShape, + ) + .padding(horizontal = 8.dp, vertical = 8.dp) + .padding(end = 2.dp, bottom = 2.dp), + ) + } + + if (isAdFillEmpty) { + loadingOverlay() + } + } +} From 12a5c0283ee87018954728495f52c8ec1d48fcb0 Mon Sep 17 00:00:00 2001 From: Teodor Grigor Date: Fri, 27 Oct 2023 16:48:57 +0300 Subject: [PATCH 09/10] Removed duplicates --- .../kotlin/dev/teogor/ceres/ads/HomeNativeAd.kt | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/app/src/main/kotlin/dev/teogor/ceres/ads/HomeNativeAd.kt b/app/src/main/kotlin/dev/teogor/ceres/ads/HomeNativeAd.kt index fb7a5b60..988fb94b 100644 --- a/app/src/main/kotlin/dev/teogor/ceres/ads/HomeNativeAd.kt +++ b/app/src/main/kotlin/dev/teogor/ceres/ads/HomeNativeAd.kt @@ -97,7 +97,7 @@ fun HomeNativeAd() { .fillMaxWidth(), viewModel = hiltViewModel(), nativeAdConfig = homeNativeAdConfig(), - adContent = { HomeNativeAdUI(it, isAdFillEmpty) }, + adContent = { it.HomeNativeAdUI(isAdFillEmpty) }, config = defaultAdLoaderConfig(adId), refreshIntervalMillis = 30_000L, onAdFillStatusChange = { isAdFillEmpty = it }, @@ -183,22 +183,9 @@ fun homeNativeAdConfig() = NativeAdConfig.Builder() @AdUI @Composable -fun HomeNativeAdUI( - nativeAdConfig: NativeAdConfig, +fun NativeAdConfig.HomeNativeAdUI( isAdFillEmpty: Boolean, ) { - val advertiserView = nativeAdConfig.advertiserView - val adChoicesView = nativeAdConfig.adChoicesView - val headlineView = nativeAdConfig.headlineView - val bodyView = nativeAdConfig.bodyView - val callToActionView = nativeAdConfig.callToActionView - val iconView = nativeAdConfig.iconView - val imageView = nativeAdConfig.imageView - val mediaView = nativeAdConfig.mediaView - val priceView = nativeAdConfig.priceView - val starRatingView = nativeAdConfig.starRatingView - val storeView = nativeAdConfig.storeView - NativeAdContainer( modifier = Modifier .fillMaxWidth() From 8f16ca7bb763995c2591d6f4cc95fa5766e89fef Mon Sep 17 00:00:00 2001 From: Teodor Grigor Date: Fri, 27 Oct 2023 17:21:15 +0300 Subject: [PATCH 10/10] Added NativeAdManager Abstract Class for Native Ad Management --- .../dev/teogor/ceres/ads/HomeNativeAd.kt | 272 +++++++++--------- .../teogor/ceres/feature/home/HomeScreen.kt | 5 +- .../ceres/feature/home/HomeViewModel.kt | 6 +- monetisation/admob/api/admob.api | 9 + .../admob/formats/nativead/NativeAdManager.kt | 82 ++++++ 5 files changed, 239 insertions(+), 135 deletions(-) create mode 100644 monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdManager.kt diff --git a/app/src/main/kotlin/dev/teogor/ceres/ads/HomeNativeAd.kt b/app/src/main/kotlin/dev/teogor/ceres/ads/HomeNativeAd.kt index 988fb94b..9e8a65a4 100644 --- a/app/src/main/kotlin/dev/teogor/ceres/ads/HomeNativeAd.kt +++ b/app/src/main/kotlin/dev/teogor/ceres/ads/HomeNativeAd.kt @@ -45,13 +45,12 @@ import com.skydoves.landscapist.glide.GlideImage import dagger.hilt.android.lifecycle.HiltViewModel import dev.teogor.ceres.framework.core.compositions.LocalNetworkConnectivity import dev.teogor.ceres.monetisation.admob.DemoAdUnitIds -import dev.teogor.ceres.monetisation.admob.annotations.AdOptions import dev.teogor.ceres.monetisation.admob.annotations.AdProperty -import dev.teogor.ceres.monetisation.admob.annotations.AdUI import dev.teogor.ceres.monetisation.admob.formats.nativead.NativeAd import dev.teogor.ceres.monetisation.admob.formats.nativead.NativeAdConfig import dev.teogor.ceres.monetisation.admob.formats.nativead.NativeAdContainer import dev.teogor.ceres.monetisation.admob.formats.nativead.NativeAdData +import dev.teogor.ceres.monetisation.admob.formats.nativead.NativeAdManager import dev.teogor.ceres.monetisation.admob.formats.nativead.NativeAdViewModel import dev.teogor.ceres.monetisation.admob.formats.nativead.RenderContent import dev.teogor.ceres.monetisation.admob.formats.nativead.createBodyView @@ -79,151 +78,160 @@ class HomeNativeAdVM @Inject constructor( homeNativeAdData: HomeNativeAdData, ) : NativeAdViewModel(homeNativeAdData) -@Composable -fun HomeNativeAd() { - val networkConnectivity = LocalNetworkConnectivity.current - val isOffline by remember { - derivedStateOf { networkConnectivity.isOffline } - } - val adId = DemoAdUnitIds.NATIVE - if (!isOffline) { - var isAdFillEmpty by remember { mutableStateOf(true) } - NativeAd( - modifier = Modifier - .padding( - horizontal = 10.dp, - vertical = 10.dp, +class HomeNativeAdBeta : NativeAdManager() { + @Composable + override fun config() = NativeAdConfig.Builder() + .headlineView( + createHeadlineView { + Text( + text = if (it.length > 25) it.take(25) else it, + color = MaterialTheme.colorScheme.onPrimaryContainer, + fontSize = 18.sp, + lineHeight = 20.sp, ) - .fillMaxWidth(), - viewModel = hiltViewModel(), - nativeAdConfig = homeNativeAdConfig(), - adContent = { it.HomeNativeAdUI(isAdFillEmpty) }, - config = defaultAdLoaderConfig(adId), - refreshIntervalMillis = 30_000L, - onAdFillStatusChange = { isAdFillEmpty = it }, - onRetrieveBackground = { - nativeAdBackground( - color = MaterialTheme.colorScheme.tertiaryContainer, - adChoicesPlacement = it, - cornerSize = 20.dp, - shadowElevation = 2.dp, + }, + ) + .bodyView( + createBodyView { + Text( + text = if (it.length > 90) it.take(90) else it, + color = MaterialTheme.colorScheme.onPrimaryContainer, + fontSize = 10.sp, + lineHeight = 12.sp, ) }, ) + .starRatingView( + createStarRatingView { + RatingBar( + rating = it, + starSize = 18.dp, + ) + }, + ) + .callToActionView( + createCallToActionView { + Text( + text = it, + color = MaterialTheme.colorScheme.onPrimary, + fontSize = 12.sp, + modifier = Modifier + .padding(top = 4.dp) + .background( + color = MaterialTheme.colorScheme.primary, + shape = ButtonDefaults.shape, + ) + .padding(horizontal = 24.dp, vertical = 8.dp), + ) + }, + ) + .iconView( + createIconView { + GlideImage( + modifier = Modifier + .size(50.dp) + .background( + color = MaterialTheme.colorScheme.background.copy(alpha = .2f), + shape = RoundedCornerShape(10.dp), + ) + .clip(RoundedCornerShape(10.dp)) + .padding(6.dp), + imageModel = { it.uri }, + imageOptions = ImageOptions( + contentScale = ContentScale.Crop, + alignment = Alignment.Center, + ), + ) + }, + ) + .build() + + @Composable + override fun IsOnline( + displayAd: @Composable () -> Unit, + ) { + val networkConnectivity = LocalNetworkConnectivity.current + val isOffline by remember { + derivedStateOf { networkConnectivity.isOffline } + } + if (!isOffline) { + displayAd() + } } -} -@AdOptions -@Composable -fun homeNativeAdConfig() = NativeAdConfig.Builder() - .headlineView( - createHeadlineView { - Text( - text = if (it.length > 25) it.take(25) else it, - color = MaterialTheme.colorScheme.onPrimaryContainer, - fontSize = 18.sp, - lineHeight = 20.sp, - ) - }, - ) - .bodyView( - createBodyView { - Text( - text = if (it.length > 90) it.take(90) else it, - color = MaterialTheme.colorScheme.onPrimaryContainer, - fontSize = 10.sp, - lineHeight = 12.sp, - ) - }, - ) - .starRatingView( - createStarRatingView { - RatingBar( - rating = it, - starSize = 18.dp, - ) - }, - ) - .callToActionView( - createCallToActionView { - Text( - text = it, - color = MaterialTheme.colorScheme.onPrimary, - fontSize = 12.sp, + @Composable + override fun Display() { + IsOnline { + val adId = DemoAdUnitIds.NATIVE + var isAdFillEmpty by remember { mutableStateOf(true) } + NativeAd( modifier = Modifier - .padding(top = 4.dp) - .background( - color = MaterialTheme.colorScheme.primary, - shape = ButtonDefaults.shape, + .padding( + horizontal = 10.dp, + vertical = 10.dp, ) - .padding(horizontal = 24.dp, vertical = 8.dp), - ) - }, - ) - .iconView( - createIconView { - GlideImage( - modifier = Modifier - .size(50.dp) - .background( - color = MaterialTheme.colorScheme.background.copy(alpha = .2f), - shape = RoundedCornerShape(10.dp), + .fillMaxWidth(), + viewModel = hiltViewModel(), + nativeAdConfig = config(), + adContent = { it.UI(isAdFillEmpty) }, + config = defaultAdLoaderConfig(adId), + refreshIntervalMillis = 30_000L, + onAdFillStatusChange = { isAdFillEmpty = it }, + onRetrieveBackground = { + nativeAdBackground( + color = MaterialTheme.colorScheme.tertiaryContainer, + adChoicesPlacement = it, + cornerSize = 20.dp, + shadowElevation = 2.dp, ) - .clip(RoundedCornerShape(10.dp)) - .padding(6.dp), - imageModel = { it.uri }, - imageOptions = ImageOptions( - contentScale = ContentScale.Crop, - alignment = Alignment.Center, - ), + }, ) - }, - ) - .build() + } + } -@AdUI -@Composable -fun NativeAdConfig.HomeNativeAdUI( - isAdFillEmpty: Boolean, -) { - NativeAdContainer( - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 10.dp, horizontal = 6.dp), - isAdFillEmpty = isAdFillEmpty, - showDefaultAdText = true, - adTextAlignment = Alignment.BottomEnd, - loadingOverlay = { - LinearProgressIndicator( - modifier = Modifier - .align(Alignment.Center) - .padding(horizontal = 20.dp) - .fillMaxWidth() - .clip(RoundedCornerShape(50)), - color = MaterialTheme.colorScheme.primary, - trackColor = MaterialTheme.colorScheme.onTertiaryContainer.blend( - MaterialTheme.colorScheme.background, - fraction = .6f, - ), - ) - }, + @Composable + override fun NativeAdConfig.UI( + isAdFillEmpty: Boolean, ) { - Row( - verticalAlignment = Alignment.CenterVertically, + NativeAdContainer( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 10.dp, horizontal = 6.dp), + isAdFillEmpty = isAdFillEmpty, + showDefaultAdText = true, + adTextAlignment = Alignment.BottomEnd, + loadingOverlay = { + LinearProgressIndicator( + modifier = Modifier + .align(Alignment.Center) + .padding(horizontal = 20.dp) + .fillMaxWidth() + .clip(RoundedCornerShape(50)), + color = MaterialTheme.colorScheme.primary, + trackColor = MaterialTheme.colorScheme.onTertiaryContainer.blend( + MaterialTheme.colorScheme.background, + fraction = .6f, + ), + ) + }, ) { - iconView.RenderContent( - modifier = Modifier.align(Alignment.Top), - ) - Column( - modifier = Modifier.padding(start = 6.dp), + Row( + verticalAlignment = Alignment.CenterVertically, ) { - headlineView.RenderContent() - starRatingView.RenderContent() - bodyView.RenderContent() + iconView.RenderContent( + modifier = Modifier.align(Alignment.Top), + ) + Column( + modifier = Modifier.padding(start = 6.dp), + ) { + headlineView.RenderContent() + starRatingView.RenderContent() + bodyView.RenderContent() + } } + callToActionView.RenderContent( + modifier = Modifier.align(Alignment.CenterHorizontally), + ) } - callToActionView.RenderContent( - modifier = Modifier.align(Alignment.CenterHorizontally), - ) } } diff --git a/app/src/main/kotlin/dev/teogor/ceres/feature/home/HomeScreen.kt b/app/src/main/kotlin/dev/teogor/ceres/feature/home/HomeScreen.kt index 98334ea4..e722ae43 100644 --- a/app/src/main/kotlin/dev/teogor/ceres/feature/home/HomeScreen.kt +++ b/app/src/main/kotlin/dev/teogor/ceres/feature/home/HomeScreen.kt @@ -20,7 +20,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.hilt.navigation.compose.hiltViewModel import dev.teogor.ceres.R -import dev.teogor.ceres.ads.HomeNativeAd +import dev.teogor.ceres.ads.HomeNativeAdBeta import dev.teogor.ceres.core.foundation.extensions.createMediaPlayer import dev.teogor.ceres.data.datastore.defaults.ceresPreferences import dev.teogor.ceres.framework.core.app.BaseActions @@ -33,6 +33,7 @@ import dev.teogor.ceres.framework.core.screen.showNavBar import dev.teogor.ceres.framework.core.screen.showSettingsButton import dev.teogor.ceres.framework.core.screen.toolbarTitle import dev.teogor.ceres.framework.core.screen.toolbarTokens +import dev.teogor.ceres.monetisation.admob.formats.nativead.NativeAd import dev.teogor.ceres.monetisation.ads.ExperimentalAdsControlApi import dev.teogor.ceres.monetisation.messaging.ConsentManager import dev.teogor.ceres.navigation.core.LocalNavigationParameters @@ -151,5 +152,5 @@ private fun HomeScreen( }, ) - HomeNativeAd() + NativeAd() } diff --git a/app/src/main/kotlin/dev/teogor/ceres/feature/home/HomeViewModel.kt b/app/src/main/kotlin/dev/teogor/ceres/feature/home/HomeViewModel.kt index 5f8a5247..38bd6b5f 100644 --- a/app/src/main/kotlin/dev/teogor/ceres/feature/home/HomeViewModel.kt +++ b/app/src/main/kotlin/dev/teogor/ceres/feature/home/HomeViewModel.kt @@ -20,6 +20,7 @@ import androidx.lifecycle.ViewModel import dagger.hilt.android.lifecycle.HiltViewModel import dev.teogor.ceres.ads.HomeBannerAd import dev.teogor.ceres.ads.HomeInterstitialAd +import dev.teogor.ceres.ads.HomeNativeAdBeta import dev.teogor.ceres.ads.HomeRewardedAd import dev.teogor.ceres.ads.HomeRewardedInterstitialAd import javax.inject.Inject @@ -30,4 +31,7 @@ class HomeViewModel @Inject constructor( val homeRewardedInterstitialAd: HomeRewardedInterstitialAd, val homeRewardedAd: HomeRewardedAd, val homeBannerAd: HomeBannerAd, -) : ViewModel() +) : ViewModel() { + + val homeNativeAd = HomeNativeAdBeta() +} diff --git a/monetisation/admob/api/admob.api b/monetisation/admob/api/admob.api index c26e059c..2beb63f3 100644 --- a/monetisation/admob/api/admob.api +++ b/monetisation/admob/api/admob.api @@ -555,6 +555,15 @@ public final class dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAd public static final fun NativeAd (Ldev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdViewModel;Landroidx/compose/ui/Modifier;Ldev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdConfig;Lkotlin/jvm/functions/Function3;Ldev/teogor/ceres/monetisation/admob/formats/nativead/AdLoaderConfig;JLkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;II)V } +public abstract class dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdManager { + public static final field $stable I + public fun ()V + public abstract fun Display (Landroidx/compose/runtime/Composer;I)V + public fun IsOnline (Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;I)V + public abstract fun UI (Ldev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdConfig;ZLandroidx/compose/runtime/Composer;I)V + public abstract fun config (Landroidx/compose/runtime/Composer;I)Ldev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdConfig; +} + public final class dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdModifiersKt { public static final fun nativeAdBackground-36lltbM (JFFLdev/teogor/ceres/monetisation/admob/models/AdChoicesPlacement;Landroidx/compose/runtime/Composer;II)Landroidx/compose/ui/Modifier; public static final fun nativeAdRoundedCornerShape-3ABfNKs (Ldev/teogor/ceres/monetisation/admob/models/AdChoicesPlacement;F)Landroidx/compose/foundation/shape/RoundedCornerShape; diff --git a/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdManager.kt b/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdManager.kt new file mode 100644 index 00000000..fe20b769 --- /dev/null +++ b/monetisation/admob/src/main/kotlin/dev/teogor/ceres/monetisation/admob/formats/nativead/NativeAdManager.kt @@ -0,0 +1,82 @@ +/* + * Copyright 2023 teogor (Teodor Grigor) + * + * 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 dev.teogor.ceres.monetisation.admob.formats.nativead + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember + +/** + * Abstract class representing a Native Ad Manager. + */ +abstract class NativeAdManager { + /** + * Retrieve the NativeAdConfig to configure the native ad. + * + * @return A [NativeAdConfig] object. + */ + @Composable + abstract fun config(): NativeAdConfig + + /** + * Display the Native Ad. + */ + @Composable + abstract fun Display() + + /** + * Composable function that provides the UI for the native ad. + * + * @param isAdFillEmpty A boolean indicating whether the ad fill is empty. + */ + @Composable + abstract fun NativeAdConfig.UI(isAdFillEmpty: Boolean) + + /** + * Check network connectivity and call the provided [displayAd] function + * if online. + * + * @param displayAd A composable function to display the ad when online. + */ + @Composable + open fun IsOnline( + displayAd: @Composable () -> Unit, + ) { + // TODO connectivity flow + displayAd() + } +} + +/** + * A composable function that displays a native ad of the specified type. + * + * @param T The type of NativeAdManager to create and display. + */ +@Composable +inline fun NativeAd() { + val adManager = remember { createNativeAdManager() } + adManager.Display() +} + +/** + * Create an instance of the specified [NativeAdManager] type. + * + * @param T The type of NativeAdManager to create. + * @return An instance of the specified [NativeAdManager]. + */ +inline fun createNativeAdManager(): T { + return T::class.java.getDeclaredConstructor().newInstance() +}