Auto commit (MindMachine) Fri Apr 10 12:01:02 PM CDT 2026

This commit is contained in:
Tretzi
2026-04-10 12:01:02 -05:00
parent 7b1c4ec474
commit 3cbd8138ee
10 changed files with 318 additions and 2 deletions

View File

@@ -0,0 +1,159 @@
package com.mindmachine.mvp.billing
import android.app.Activity
import android.content.Context
import com.android.billingclient.api.AcknowledgePurchaseParams
import com.android.billingclient.api.BillingClient
import com.android.billingclient.api.BillingClientStateListener
import com.android.billingclient.api.BillingFlowParams
import com.android.billingclient.api.BillingResult
import com.android.billingclient.api.ProductDetails
import com.android.billingclient.api.ProductDetailsResponseListener
import com.android.billingclient.api.Purchase
import com.android.billingclient.api.PurchasesResponseListener
import com.android.billingclient.api.QueryProductDetailsParams
import com.android.billingclient.api.QueryPurchasesParams
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
class BillingManager(
context: Context,
private val onNoAdsEntitlementChanged: (Boolean) -> Unit,
) {
private val appContext = context.applicationContext
private val _hasNoAds = MutableStateFlow(false)
val hasNoAds: StateFlow<Boolean> = _hasNoAds.asStateFlow()
private val _productDetails = MutableStateFlow<Map<String, ProductDetails>>(emptyMap())
val productDetails: StateFlow<Map<String, ProductDetails>> = _productDetails.asStateFlow()
private val billingClient: BillingClient = BillingClient.newBuilder(appContext)
.setListener { _, purchases ->
if (purchases != null) {
handlePurchases(purchases)
}
}
.enablePendingPurchases()
.build()
fun start() {
if (billingClient.isReady) {
refresh()
return
}
billingClient.startConnection(object : BillingClientStateListener {
override fun onBillingSetupFinished(result: BillingResult) {
if (result.responseCode == BillingClient.BillingResponseCode.OK) {
refresh()
}
}
override fun onBillingServiceDisconnected() {
// No-op, will retry on next call.
}
})
}
fun refresh() {
queryProductDetails()
queryActivePurchases()
}
fun launchPurchase(activity: Activity, productId: String) {
val details = _productDetails.value[productId] ?: return
val productDetailsParams = when (details.productType) {
BillingClient.ProductType.SUBS -> {
val offerToken = details.subscriptionOfferDetails
?.firstOrNull()
?.offerToken
?: return
BillingFlowParams.ProductDetailsParams.newBuilder()
.setProductDetails(details)
.setOfferToken(offerToken)
.build()
}
BillingClient.ProductType.INAPP -> {
BillingFlowParams.ProductDetailsParams.newBuilder()
.setProductDetails(details)
.build()
}
else -> return
}
val params = BillingFlowParams.newBuilder()
.setProductDetailsParamsList(listOf(productDetailsParams))
.build()
billingClient.launchBillingFlow(activity, params)
}
private fun queryProductDetails() {
if (!billingClient.isReady) return
val products = listOf(
QueryProductDetailsParams.Product.newBuilder()
.setProductId(BillingProducts.SUB_REMOVE_ADS_MONTHLY)
.setProductType(BillingClient.ProductType.SUBS)
.build(),
QueryProductDetailsParams.Product.newBuilder()
.setProductId(BillingProducts.INAPP_REMOVE_ADS_FOREVER)
.setProductType(BillingClient.ProductType.INAPP)
.build(),
)
val params = QueryProductDetailsParams.newBuilder()
.setProductList(products)
.build()
billingClient.queryProductDetailsAsync(params, ProductDetailsResponseListener { result, detailsList ->
if (result.responseCode == BillingClient.BillingResponseCode.OK) {
_productDetails.value = detailsList.associateBy { it.productId }
}
})
}
private fun queryActivePurchases() {
if (!billingClient.isReady) return
val listener = PurchasesResponseListener { result, purchases ->
if (result.responseCode == BillingClient.BillingResponseCode.OK) {
handlePurchases(purchases)
}
}
billingClient.queryPurchasesAsync(
QueryPurchasesParams.newBuilder().setProductType(BillingClient.ProductType.SUBS).build(),
listener
)
billingClient.queryPurchasesAsync(
QueryPurchasesParams.newBuilder().setProductType(BillingClient.ProductType.INAPP).build(),
listener
)
}
private fun handlePurchases(purchases: List<Purchase>) {
val active = purchases.any { p ->
p.purchaseState == Purchase.PurchaseState.PURCHASED &&
p.products.any { it == BillingProducts.SUB_REMOVE_ADS_MONTHLY || it == BillingProducts.INAPP_REMOVE_ADS_FOREVER }
}
if (_hasNoAds.value != active) {
_hasNoAds.value = active
onNoAdsEntitlementChanged(active)
}
// Acknowledge where needed
purchases.filter { it.purchaseState == Purchase.PurchaseState.PURCHASED && !it.isAcknowledged }
.forEach { purchase ->
val params = AcknowledgePurchaseParams.newBuilder()
.setPurchaseToken(purchase.purchaseToken)
.build()
billingClient.acknowledgePurchase(params) { /* no-op */ }
}
}
}