diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 5e70723..cc95807 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -15,8 +15,8 @@ android { applicationId = "solutions.tretter.mindmachine" minSdk = 26 targetSdk = 35 - versionCode = 7 - versionName = "1.0.2" + versionCode = 11 + versionName = "1.0.6" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } diff --git a/app/src/main/java/com/mindmachine/mvp/billing/.BillingManager.kt.swp b/app/src/main/java/com/mindmachine/mvp/billing/.BillingManager.kt.swp deleted file mode 100644 index c7eaa0a..0000000 Binary files a/app/src/main/java/com/mindmachine/mvp/billing/.BillingManager.kt.swp and /dev/null differ diff --git a/app/src/main/java/com/mindmachine/mvp/billing/BillingManager.kt b/app/src/main/java/com/mindmachine/mvp/billing/BillingManager.kt index 6d0ae51..91606b9 100644 --- a/app/src/main/java/com/mindmachine/mvp/billing/BillingManager.kt +++ b/app/src/main/java/com/mindmachine/mvp/billing/BillingManager.kt @@ -9,7 +9,8 @@ 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.QueryProductDetailsResult +import com.android.billingclient.api.PendingPurchasesParams import com.android.billingclient.api.Purchase import com.android.billingclient.api.PurchasesResponseListener import com.android.billingclient.api.QueryProductDetailsParams @@ -17,6 +18,8 @@ import com.android.billingclient.api.QueryPurchasesParams import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import android.os.Handler +import android.os.Looper class BillingManager( context: Context, @@ -44,30 +47,50 @@ class BillingManager( handlePurchases(purchases) } } - .enablePendingPurchases() + .enablePendingPurchases( + PendingPurchasesParams.newBuilder() + .enableOneTimeProducts() + .build() + ) .build() + @Volatile + private var isConnecting = false + fun start() { - log("BillingManager.start() - isReady=${billingClient.isReady}") + log("BillingManager.start() - isReady=${billingClient.isReady}, isConnecting=$isConnecting") + if (billingClient.isReady) { log("Billing client already ready") refresh() return } + + if (isConnecting) { + log("Billing client connection already in progress") + return + } + + isConnecting = true + log("Calling startConnection()") + billingClient.startConnection(object : BillingClientStateListener { override fun onBillingSetupFinished(result: BillingResult) { + isConnecting = false log("Billing setup finished: ${result.responseCode} ${result.debugMessage}") + if (result.responseCode == BillingClient.BillingResponseCode.OK) { - refresh() + Handler(Looper.getMainLooper()).post { + refresh() + } } else { logError("Billing setup failed: ${result.responseCode} ${result.debugMessage}") } } - + override fun onBillingServiceDisconnected() { - log("Billing service disconnected - attempting reconnect") - // Auto-reconnection handled by billing client, retry connection - start() + isConnecting = false + log("Billing service disconnected") } }) } @@ -131,37 +154,53 @@ class BillingManager( log("Billing client not ready") return } - - // Check if product details feature is supported + val featureResult = billingClient.isFeatureSupported(BillingClient.FeatureType.PRODUCT_DETAILS) + log("PRODUCT_DETAILS support: ${featureResult.responseCode} ${featureResult.debugMessage}") if (featureResult.responseCode != BillingClient.BillingResponseCode.OK) { - logError("PRODUCT_DETAILS feature not supported: ${featureResult.responseCode}") + logError("PRODUCT_DETAILS feature not supported") return } - - val products = listOf( + + val requestedProducts = listOf( + BillingProducts.SUB_REMOVE_ADS_MONTHLY to BillingClient.ProductType.SUBS, + BillingProducts.INAPP_REMOVE_ADS_FOREVER to BillingClient.ProductType.INAPP, + ) + + log("Querying product details for: $requestedProducts") + + val products = requestedProducts.map { (id, type) -> 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(), + .setProductId(id) + .setProductType(type) + .build() + } + + log( + "Querying product details for: " + + "${BillingProducts.SUB_REMOVE_ADS_MONTHLY}:${BillingClient.ProductType.SUBS}, " + + "${BillingProducts.INAPP_REMOVE_ADS_FOREVER}:${BillingClient.ProductType.INAPP}" ) val params = QueryProductDetailsParams.newBuilder() .setProductList(products) .build() - - billingClient.queryProductDetailsAsync(params, ProductDetailsResponseListener { result, detailsList -> + + log("Calling queryProductDetailsAsync now") + + billingClient.queryProductDetailsAsync(params) { result, queryResult -> + log("queryProductDetails result: ${result.responseCode} ${result.debugMessage}") + + val detailsList = queryResult.productDetailsList + log("Fetched products: ${detailsList.map { it.productId }}") + + val unfetched = queryResult.unfetchedProductList + log("Unfetched products: ${unfetched.map { "${it.productId}:${it.statusCode}" }}") + if (result.responseCode == BillingClient.BillingResponseCode.OK) { - log("Product details loaded: ${detailsList.size} items") _productDetails.value = detailsList.associateBy { it.productId } - } else { - logError("queryProductDetails failed: ${result.responseCode} ${result.debugMessage}") } - }) + } } private fun queryActivePurchases() {