Skip to content
This repository has been archived by the owner on Oct 6, 2024. It is now read-only.

Commit

Permalink
Merge pull request #6 from matsumo0922/ads
Browse files Browse the repository at this point in the history
Introduce native layout
  • Loading branch information
matsumo0922 authored Dec 13, 2023
2 parents f8ee41e + dd83720 commit 597c5b3
Show file tree
Hide file tree
Showing 25 changed files with 825 additions and 52 deletions.
40 changes: 37 additions & 3 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,29 @@ android {
else -> "FANBOX"
}

it.resValues.put(it.makeResValueKey("string", "app_name"), ResValue(appName, null))
// This ID must be valid or the app will crash.
// When building from GitHub, either exclude AdMob code or register with AdMob for an ID.
val admobTestAppId = "ca-app-pub-0000000000000000~0000000000"
val bannerAdTestId = "ca-app-pub-3940256099942544/6300978111"
val nativeAdTestId = "ca-app-pub-3940256099942544/2247696110"

it.manifestPlaceholders.apply {
putManifestPlaceholder(localProperties, "ADMOB_APP_ID", defaultValue = admobTestAppId)
}

it.resValues.apply {
put(it.makeResValueKey("string", "app_name"), ResValue(appName, null))
}

it.buildConfigFields.apply {
putBuildConfig(localProperties, "VERSION_NAME", libs.versions.versionName.get().toStringLiteral())
putBuildConfig(localProperties, "VERSION_CODE", libs.versions.versionCode.get().toStringLiteral())
putBuildConfig(localProperties, "DEVELOPER_PASSWORD")
putBuildConfig(localProperties, "PIXIV_CLIENT_ID")
putBuildConfig(localProperties, "PIXIV_CLIENT_SECRET")
putBuildConfig(localProperties, "ADMOB_APP_ID", defaultValue = admobTestAppId)
putBuildConfig(localProperties, "ADMOB_BANNER_AD_UNIT_ID", if (it.buildType != "release") bannerAdTestId else null)
putBuildConfig(localProperties, "ADMOB_NATIVE_AD_UNIT_ID", if (it.buildType != "release") nativeAdTestId else null)
}

if (it.buildType == "release") {
Expand Down Expand Up @@ -124,6 +140,7 @@ dependencies {

implementation(libs.androidx.core.splashscreen)
implementation(libs.play.service.oss)
implementation(libs.play.service.ads)
implementation(libs.google.material)

debugImplementation(libs.facebook.flipper)
Expand All @@ -138,18 +155,35 @@ fun MapProperty<String, BuildConfigField<out Serializable>>.putBuildConfig(
key: String,
value: String? = null,
type: String = "String",
defaultValue: String = "",
comment: String? = null
) {
val data = value?.toStringLiteral()
val property = localProperties.getProperty(key)?.toStringLiteral()
val env = System.getenv(key)?.toStringLiteral()
val defaultData = defaultValue.toStringLiteral()

put(key, BuildConfigField(type, data ?: property ?: env ?: defaultData, comment))
}

fun MapProperty<String, String>.putManifestPlaceholder(
localProperties: Properties,
key: String,
value: String? = null,
defaultValue: String = "",
) {
val data = value?.toStringLiteral()
val property = localProperties.getProperty(key)?.toStringLiteral()
val env = System.getenv(key)?.toStringLiteral()
val defaultData = defaultValue.toStringLiteral()

put(key, BuildConfigField(type, value ?: property ?: env ?: "\"\"", comment))
put(key, data ?: property ?: env ?: defaultData)
}

fun Any.toStringLiteral(): String {
val value = toString()

if (value.first() == '\"' && value.last() == '\"') {
if (value.firstOrNull() == '\"' && value.lastOrNull() == '\"') {
return value
}

Expand Down
7 changes: 5 additions & 2 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,11 @@
<provider
android:name="com.google.firebase.provider.FirebaseInitProvider"
android:authorities="${applicationId}.firebaseinitprovider"
tools:node="remove"
/>
tools:node="remove" />

<meta-data
android:name="com.google.android.gms.ads.APPLICATION_ID"
android:value="${ADMOB_APP_ID}" />

</application>

Expand Down
3 changes: 3 additions & 0 deletions app/src/main/java/caios/android/fanbox/PixiViewApplication.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import coil.decode.GifDecoder
import coil.decode.ImageDecoderDecoder
import coil.disk.DiskCache
import coil.memory.MemoryCache
import com.google.android.gms.ads.MobileAds
import com.google.android.material.color.DynamicColors
import com.google.firebase.FirebaseApp
import com.google.firebase.FirebaseOptions
Expand All @@ -30,6 +31,8 @@ class PixiViewApplication : Application() {

Timber.plant(PixiViewDebugTree())

MobileAds.initialize(this)

DynamicColors.applyToActivitiesIfAvailable(this)

Thread.setDefaultUncaughtExceptionHandler { _, e ->
Expand Down
3 changes: 3 additions & 0 deletions app/src/main/java/caios/android/fanbox/di/AppModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ object AppModule {
developerPassword = BuildConfig.DEVELOPER_PASSWORD,
pixivClientId = BuildConfig.PIXIV_CLIENT_ID,
pixivClientSecret = BuildConfig.PIXIV_CLIENT_SECRET,
adMobAppId = BuildConfig.ADMOB_APP_ID,
adMobBannerAdUnitId = BuildConfig.ADMOB_BANNER_AD_UNIT_ID,
adMobNativeAdUnitId = BuildConfig.ADMOB_NATIVE_AD_UNIT_ID,
)
}
}
2 changes: 2 additions & 0 deletions config/detekt/detekt.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ style:

potential-bugs:
active: true
UnsafeCallOnNullableType:
active: false

formatting:
active: true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ class BillingClientProviderImpl @Inject constructor(
billingClient.queryPurchaseHistoryAsync(params) { result, purchases ->
when (val response = result.toResponse()) {
is BillingResponse.OK -> {
listener.invoke(Result.success(purchases ?: emptyList()))
listener.invoke(Result.success(purchases.orEmpty()))
}
is BillingResponse.ServiceDisconnected, is BillingResponse.ServiceError -> {
Timber.d("queryPurchaseHistory: service error. CODE=${response.code}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ data class PurchaseSingleCommand(
fun toBillingFlowParams(): BillingFlowParams {
val productDetailParams = ProductDetailsParams.newBuilder()
.setProductDetails(productDetails.rawProductDetails)
.setOfferToken(offerToken ?: "")
.setOfferToken(offerToken.orEmpty())
.build()

val builder = BillingFlowParams.newBuilder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ data class PixiViewConfig(
val developerPassword: String,
val pixivClientId: String,
val pixivClientSecret: String,
val adMobAppId: String,
val adMobBannerAdUnitId: String,
val adMobNativeAdUnitId: String,
) {
companion object {
fun dummy(): PixiViewConfig {
Expand All @@ -21,6 +24,9 @@ data class PixiViewConfig(
developerPassword = "1919191919",
pixivClientId = "1919191919",
pixivClientSecret = "1919191919",
adMobAppId = "ca-app-pub-1919191919~1919191919",
adMobBannerAdUnitId = "ca-app-pub-1919191919/1919191919",
adMobNativeAdUnitId = "ca-app-pub-1919191919/1919191919",
)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
@file:Suppress("InjectDispatcher")

package caios.android.fanbox.core.common.di

import dagger.Module
Expand Down
1 change: 1 addition & 0 deletions core/ui/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ dependencies {
implementation(libs.androidx.biomtrics)
implementation(libs.androidx.biomtrics.ktx)
implementation(libs.androidx.palette)
implementation(libs.play.service.ads)
implementation(libs.collapsing.toolbar.compose)
implementation(libs.reorderble.compose)
}
197 changes: 197 additions & 0 deletions core/ui/src/main/java/caios/android/fanbox/core/ui/ads/NativeAdItem.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
package caios.android.fanbox.core.ui.ads

import android.annotation.SuppressLint
import android.content.res.ColorStateList
import android.view.View
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.surfaceColorAtElevation
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidViewBinding
import androidx.core.view.doOnLayout
import caios.android.fanbox.core.ui.databinding.LayoutNativeAdsMediumBinding
import com.google.android.gms.ads.AdLoader
import com.google.android.gms.ads.AdRequest
import com.google.android.gms.ads.nativead.NativeAd
import com.google.android.gms.ads.nativead.NativeAdOptions

/*@SuppressLint("MissingPermission")
@Composable
fun NativeAdSmallItem(
nativeAdUnitId: String,
nativeAdsPreLoader: NativeAdsPreLoader,
modifier: Modifier = Modifier,
) {
val titleTextColor = MaterialTheme.colorScheme.onSurface.toArgb()
val bodyTextColor = MaterialTheme.colorScheme.onSurfaceVariant.toArgb()
val buttonColor = MaterialTheme.colorScheme.primary.toArgb()
val buttonTextColor = MaterialTheme.colorScheme.onPrimary.toArgb()
Card(
modifier = modifier.clip(RoundedCornerShape(8.dp)),
shape = RoundedCornerShape(8.dp),
colors = CardDefaults.cardColors(MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp)),
) {
AndroidViewBinding(
modifier = Modifier.fillMaxWidth(),
factory = { inflater, parent, attachToParent ->
val binding = LayoutNativeAdsSmallBinding.inflate(inflater, parent, attachToParent)
binding.tvBody.setTextColor(bodyTextColor)
binding.btnCta.setTextColor(buttonTextColor)
binding.btnCta.backgroundTintList = ColorStateList.valueOf(buttonColor)
binding.tvHeadline.setTextColor(titleTextColor)
binding.tvStore.setTextColor(bodyTextColor)
binding
},
update = {
val adView = root.also { adView ->
adView.bodyView = tvBody
adView.callToActionView = btnCta
adView.headlineView = tvHeadline
adView.iconView = ivAppIcon
adView.storeView = tvStore
}
fun setupNativeAd(nativeAd: NativeAd) {
nativeAd.body?.let { tvBody.text = it }
nativeAd.callToAction?.let { btnCta.text = it }
nativeAd.headline?.let { tvHeadline.text = it }
nativeAd.icon?.let { ivAppIcon.setImageDrawable(it.drawable) }
nativeAd.store?.let { tvStore.text = it }
adView.setNativeAd(nativeAd)
}
val preloadedNativeAd = nativeAdsPreLoader.popAd()
if (preloadedNativeAd != null) {
setupNativeAd(preloadedNativeAd)
} else {
adView.doOnLayout {
val adLoader = AdLoader.Builder(adView.context, nativeAdUnitId)
.forNativeAd { nativeAd ->
setupNativeAd(nativeAd)
}
.withNativeAdOptions(NativeAdOptions.Builder().build())
.build()
adLoader.loadAd(AdRequest.Builder().build())
}
}
}
)
}
}*/

@SuppressLint("MissingPermission")
@Composable
fun NativeAdMediumItem(
nativeAdUnitId: String,
nativeAdsPreLoader: NativeAdsPreLoader,
modifier: Modifier = Modifier,
) {
fun setupNativeAd(binding: LayoutNativeAdsMediumBinding, nativeAd: NativeAd) {
val adView = binding.root.also { adView ->
adView.advertiserView = binding.tvAdvertiser
adView.bodyView = binding.tvBody
adView.callToActionView = binding.btnCta
adView.headlineView = binding.tvHeadline
adView.iconView = binding.ivAppIcon
adView.priceView = binding.tvPrice
adView.starRatingView = binding.rtbStars
adView.storeView = binding.tvStore
adView.mediaView = binding.mvContent
}

nativeAd.advertiser?.let { binding.tvAdvertiser.text = it }
nativeAd.body?.let { binding.tvBody.text = it }
nativeAd.callToAction?.let { binding.btnCta.text = it }
nativeAd.headline?.let { binding.tvHeadline.text = it }
nativeAd.icon?.let { binding.ivAppIcon.setImageDrawable(it.drawable) }
nativeAd.price?.let { binding.tvPrice.text = it }
nativeAd.starRating?.let { binding.rtbStars.rating = it.toFloat() }
nativeAd.store?.let { binding.tvStore.text = it }

binding.tvAdvertiser.visibility = if (nativeAd.advertiser.isNullOrBlank()) View.GONE else View.VISIBLE

adView.setNativeAd(nativeAd)
}

val context = LocalContext.current

var nativeAdKey by rememberSaveable { mutableIntStateOf(-1) }
var isAdLoaded by rememberSaveable { mutableStateOf(false) }

val titleTextColor = MaterialTheme.colorScheme.onSurface.toArgb()
val bodyTextColor = MaterialTheme.colorScheme.onSurfaceVariant.toArgb()
val buttonColor = MaterialTheme.colorScheme.primary.toArgb()
val buttonTextColor = MaterialTheme.colorScheme.onPrimary.toArgb()

DisposableEffect(!isAdLoaded) {
if (!isAdLoaded) {
nativeAdKey = nativeAdsPreLoader.getKey() ?: -1
isAdLoaded = true
}

onDispose {
nativeAdsPreLoader.popAd(nativeAdKey)
}
}

if (isAdLoaded) {
Card(
modifier = modifier.clip(RoundedCornerShape(8.dp)),
shape = RoundedCornerShape(8.dp),
colors = CardDefaults.cardColors(MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp)),
) {
AndroidViewBinding(
modifier = Modifier.fillMaxWidth(),
factory = { inflater, parent, attachToParent ->
val binding = LayoutNativeAdsMediumBinding.inflate(inflater, parent, attachToParent)

binding.tvAdvertiser.setTextColor(bodyTextColor)
binding.tvBody.setTextColor(bodyTextColor)
binding.btnCta.setTextColor(buttonTextColor)
binding.btnCta.backgroundTintList = ColorStateList.valueOf(buttonColor)
binding.tvHeadline.setTextColor(titleTextColor)
binding.tvPrice.setTextColor(bodyTextColor)
binding.tvStore.setTextColor(bodyTextColor)
binding.tvAd.setTextColor(bodyTextColor)

binding
},
update = {
this.root.doOnLayout {
if (nativeAdKey != -1) {
nativeAdsPreLoader.getAd(nativeAdKey)?.let {
setupNativeAd(this, it)
return@doOnLayout
}
}

val adLoader = AdLoader.Builder(context, nativeAdUnitId)
.forNativeAd { setupNativeAd(this, it) }
.withNativeAdOptions(NativeAdOptions.Builder().build())
.build()

adLoader.loadAd(AdRequest.Builder().build())
}
},
)
}
}
}
Loading

0 comments on commit 597c5b3

Please sign in to comment.