Auto commit (MindMachine) Sat Apr 11 01:01:02 PM CDT 2026

This commit is contained in:
Tretzi
2026-04-11 13:01:03 -05:00
parent ddc497a91f
commit adbfa0754b
5 changed files with 162 additions and 27 deletions

View File

@@ -16,6 +16,9 @@ import com.android.billingclient.api.QueryPurchasesParams
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
class BillingManager(
context: Context,
@@ -29,6 +32,17 @@ class BillingManager(
private val _productDetails = MutableStateFlow<Map<String, ProductDetails>>(emptyMap())
val productDetails: StateFlow<Map<String, ProductDetails>> = _productDetails.asStateFlow()
private val _logs = MutableStateFlow<List<String>>(emptyList())
val logs: StateFlow<List<String>> = _logs.asStateFlow()
private fun log(message: String) {
val timestamp = SimpleDateFormat("HH:mm:ss.SSS", Locale.getDefault()).format(Date())
_logs.value = _logs.value + "[$timestamp] $message"
if (_logs.value.size > 50) {
_logs.value = _logs.value.takeLast(50)
}
}
private val billingClient: BillingClient = BillingClient.newBuilder(appContext)
.setListener { _, purchases ->
if (purchases != null) {
@@ -39,37 +53,50 @@ class BillingManager(
.build()
fun start() {
log("BillingManager.start()")
if (billingClient.isReady) {
log("Billing client already ready")
refresh()
return
}
billingClient.startConnection(object : BillingClientStateListener {
override fun onBillingSetupFinished(result: BillingResult) {
log("Billing setup finished: ${result.responseCode} ${result.debugMessage}")
if (result.responseCode == BillingClient.BillingResponseCode.OK) {
refresh()
}
}
override fun onBillingServiceDisconnected() {
log("Billing service disconnected")
// No-op, will retry on next call.
}
})
}
fun refresh() {
log("refresh() called")
queryProductDetails()
queryActivePurchases()
}
fun launchPurchase(activity: Activity, productId: String) {
val details = _productDetails.value[productId] ?: return
log("launchPurchase() productId=$productId")
val details = _productDetails.value[productId]
if (details == null) {
log("Product details not found for $productId")
return
}
val productDetailsParams = when (details.productType) {
BillingClient.ProductType.SUBS -> {
val offerToken = details.subscriptionOfferDetails
?.firstOrNull()
?.offerToken
?: return
if (offerToken == null) {
log("No subscription offer token for $productId")
return
}
BillingFlowParams.ProductDetailsParams.newBuilder()
.setProductDetails(details)
.setOfferToken(offerToken)
@@ -82,18 +109,26 @@ class BillingManager(
.build()
}
else -> return
else -> {
log("Unknown product type for $productId")
return
}
}
val params = BillingFlowParams.newBuilder()
.setProductDetailsParamsList(listOf(productDetailsParams))
.build()
billingClient.launchBillingFlow(activity, params)
val result = billingClient.launchBillingFlow(activity, params)
log("launchBillingFlow result: ${result.responseCode} ${result.debugMessage}")
}
private fun queryProductDetails() {
if (!billingClient.isReady) return
log("queryProductDetails()")
if (!billingClient.isReady) {
log("Billing client not ready")
return
}
val products = listOf(
QueryProductDetailsParams.Product.newBuilder()
@@ -111,16 +146,23 @@ class BillingManager(
.build()
billingClient.queryProductDetailsAsync(params, ProductDetailsResponseListener { result, detailsList ->
log("queryProductDetails result: ${result.responseCode} ${result.debugMessage}")
if (result.responseCode == BillingClient.BillingResponseCode.OK) {
_productDetails.value = detailsList.associateBy { it.productId }
log("Product details loaded: ${detailsList.size} items")
}
})
}
private fun queryActivePurchases() {
if (!billingClient.isReady) return
log("queryActivePurchases()")
if (!billingClient.isReady) {
log("Billing client not ready")
return
}
val listener = PurchasesResponseListener { result, purchases ->
log("Query purchases result: ${result.responseCode} ${result.debugMessage}")
if (result.responseCode == BillingClient.BillingResponseCode.OK) {
handlePurchases(purchases)
}
@@ -137,12 +179,15 @@ class BillingManager(
}
private fun handlePurchases(purchases: List<Purchase>) {
log("handlePurchases() count=${purchases.size}")
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 }
}
log("Active entitlement: $active")
if (_hasNoAds.value != active) {
log("Entitlement changed: ${_hasNoAds.value} -> $active")
_hasNoAds.value = active
onNoAdsEntitlementChanged(active)
}
@@ -150,6 +195,7 @@ class BillingManager(
// Acknowledge where needed
purchases.filter { it.purchaseState == Purchase.PurchaseState.PURCHASED && !it.isAcknowledged }
.forEach { purchase ->
log("Acknowledging purchase: ${purchase.purchaseToken}")
val params = AcknowledgePurchaseParams.newBuilder()
.setPurchaseToken(purchase.purchaseToken)
.build()