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..9e8a65a4 --- /dev/null +++ b/app/src/main/kotlin/dev/teogor/ceres/ads/HomeNativeAd.kt @@ -0,0 +1,237 @@ +/* + * 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.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.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.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.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.AdProperty +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 +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.monetisation.admob.formats.nativead.defaultAdLoaderConfig +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 +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) + +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, + ) + }, + ) + .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() + } + } + + @Composable + override fun Display() { + IsOnline { + val adId = DemoAdUnitIds.NATIVE + var isAdFillEmpty by remember { mutableStateOf(true) } + NativeAd( + modifier = Modifier + .padding( + horizontal = 10.dp, + vertical = 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, + ) + }, + ) + } + } + + @Composable + override fun NativeAdConfig.UI( + 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, + ), + ) + }, + ) { + 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/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..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 @@ -16,24 +16,11 @@ 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.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 @@ -46,15 +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.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,8 +43,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 @Composable @@ -175,82 +152,5 @@ private fun HomeScreen( }, ) - val adId = DemoAdUnitIds.NATIVE - val nativeAd by remember { - homeVM.nativeAd - } - if (!isOffline) { - val nativeAdConfig = nativeAdConfig() - - 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 = { - NativeAdUi(nativeAdConfig) - }, - nativeAd = nativeAd, - config = AdLoaderConfig(adId), - refreshIntervalMillis = 30000L, - onAdLoaded = { - homeVM.setNativeAd(it) - }, - ) - } + NativeAd() } - -@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/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..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 @@ -16,12 +16,11 @@ 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 +import dev.teogor.ceres.ads.HomeNativeAdBeta import dev.teogor.ceres.ads.HomeRewardedAd import dev.teogor.ceres.ads.HomeRewardedInterstitialAd import javax.inject.Inject @@ -34,10 +33,5 @@ class HomeViewModel @Inject constructor( val homeBannerAd: HomeBannerAd, ) : ViewModel() { - var nativeAd = mutableStateOf(null) - private set - - fun setNativeAd(ad: NativeAd) { - nativeAd.value = ad - } + val homeNativeAd = HomeNativeAdBeta() } diff --git a/monetisation/admob/api/admob.api b/monetisation/admob/api/admob.api index 73602f94..2beb63f3 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; @@ -384,35 +396,41 @@ 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 getAdChoicesPlacement ()Ldev/teogor/ceres/monetisation/admob/models/AdChoicesPlacement; 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 getMediaAspectRatio ()Ldev/teogor/ceres/monetisation/admob/models/NativeMediaAspectRatio; 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;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;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 (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; + 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 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; } @@ -420,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 @@ -437,6 +462,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; @@ -510,12 +536,45 @@ 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 + 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 + 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; + public static synthetic fun nativeAdRoundedCornerShape-3ABfNKs$default (Ldev/teogor/ceres/monetisation/admob/models/AdChoicesPlacement;FILjava/lang/Object;)Landroidx/compose/foundation/shape/RoundedCornerShape; } -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 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 { @@ -526,3 +585,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/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/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..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 @@ -21,33 +21,42 @@ 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 -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 +import dev.teogor.ceres.monetisation.admob.models.AdChoicesPlacement @Composable -fun NativeAd( +fun NativeAd( + viewModel: T, modifier: Modifier = Modifier, nativeAdConfig: NativeAdConfig, - adContent: @Composable () -> Unit, - nativeAd: NativeAd?, - config: AdLoaderConfig = AdLoaderConfig(""), + adContent: @Composable (NativeAdConfig) -> Unit, + config: AdLoaderConfig, refreshIntervalMillis: Long = 30000L, onAdEvent: (AdEvent) -> Unit = {}, - onAdLoaded: (NativeAd) -> Unit = {}, + onAdFillStatusChange: (Boolean) -> Unit = {}, + onRetrieveBackground: @Composable (AdChoicesPlacement) -> Modifier = { Modifier }, ) { + val nativeAd by remember { viewModel.nativeAd } var adView by remember { mutableStateOf(null) } + var isAdFillEmpty by rememberSaveable { mutableStateOf(true) } + LaunchedEffect(isAdFillEmpty) { + onAdFillStatusChange(isAdFillEmpty) + } val adLoader = rememberAdLoader( config = config, onAdEvent = onAdEvent, - onNativeAd = onAdLoaded, + onNativeAd = { + viewModel.setNativeAd(it) + }, ) RefreshableNativeAd( @@ -56,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 -> @@ -76,46 +86,51 @@ fun NativeAd( adview.adChoicesView } - composeView.setContent(adContent) + composeView.setContent { + adContent(nativeAdConfig) + } } } LaunchedEffect(nativeAd) { - nativeAd?.let { nativeAd -> - nativeAd.body?.let { body -> + if (nativeAd == null) { + isAdFillEmpty = true + } else { + isAdFillEmpty = false + 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!!) } } } 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, 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() + } + } +} 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/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() +} 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, + ) + } +} 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/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)) +} 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), +} 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), + ) + } + } +}