Billing: replace in-app logs with ADB logging, add auto-reconnection
This commit is contained in:
@@ -1256,7 +1256,6 @@ fun RemoveAdsScreen(billingManager: solutions.tretter.mindmachine.billing.Billin
|
|||||||
val ui by vm.ui.collectAsStateWithLifecycle()
|
val ui by vm.ui.collectAsStateWithLifecycle()
|
||||||
val activity = LocalContext.current as? Activity
|
val activity = LocalContext.current as? Activity
|
||||||
val details by billingManager.productDetails.collectAsStateWithLifecycle()
|
val details by billingManager.productDetails.collectAsStateWithLifecycle()
|
||||||
val logs by billingManager.logs.collectAsStateWithLifecycle()
|
|
||||||
|
|
||||||
val subDetails = details[solutions.tretter.mindmachine.billing.BillingProducts.SUB_REMOVE_ADS_MONTHLY]
|
val subDetails = details[solutions.tretter.mindmachine.billing.BillingProducts.SUB_REMOVE_ADS_MONTHLY]
|
||||||
val subPrice = subDetails
|
val subPrice = subDetails
|
||||||
@@ -1308,21 +1307,6 @@ fun RemoveAdsScreen(billingManager: solutions.tretter.mindmachine.billing.Billin
|
|||||||
"Note: Purchases only work when the app is installed from Google Play (Internal testing/Production). Debug sideloaded builds won’t complete real billing flows.",
|
"Note: Purchases only work when the app is installed from Google Play (Internal testing/Production). Debug sideloaded builds won’t complete real billing flows.",
|
||||||
color = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.8f)
|
color = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.8f)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Debug logs
|
|
||||||
Text("Billing logs:", style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onBackground)
|
|
||||||
Column(
|
|
||||||
Modifier
|
|
||||||
.weight(1f)
|
|
||||||
.fillMaxWidth()
|
|
||||||
.verticalScroll(rememberScrollState())
|
|
||||||
.background(MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.2f))
|
|
||||||
.padding(8.dp)
|
|
||||||
) {
|
|
||||||
logs.forEach { log ->
|
|
||||||
Text(log, color = MaterialTheme.colorScheme.onBackground, style = MaterialTheme.typography.bodySmall)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package solutions.tretter.mindmachine.billing
|
|||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.util.Log
|
||||||
import com.android.billingclient.api.AcknowledgePurchaseParams
|
import com.android.billingclient.api.AcknowledgePurchaseParams
|
||||||
import com.android.billingclient.api.BillingClient
|
import com.android.billingclient.api.BillingClient
|
||||||
import com.android.billingclient.api.BillingClientStateListener
|
import com.android.billingclient.api.BillingClientStateListener
|
||||||
@@ -16,9 +17,6 @@ import com.android.billingclient.api.QueryPurchasesParams
|
|||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import java.text.SimpleDateFormat
|
|
||||||
import java.util.Date
|
|
||||||
import java.util.Locale
|
|
||||||
|
|
||||||
class BillingManager(
|
class BillingManager(
|
||||||
context: Context,
|
context: Context,
|
||||||
@@ -32,15 +30,12 @@ class BillingManager(
|
|||||||
private val _productDetails = MutableStateFlow<Map<String, ProductDetails>>(emptyMap())
|
private val _productDetails = MutableStateFlow<Map<String, ProductDetails>>(emptyMap())
|
||||||
val productDetails: StateFlow<Map<String, ProductDetails>> = _productDetails.asStateFlow()
|
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) {
|
private fun log(message: String) {
|
||||||
val timestamp = SimpleDateFormat("HH:mm:ss.SSS", Locale.getDefault()).format(Date())
|
Log.d("MindMachineBilling", message)
|
||||||
_logs.value = _logs.value + "[$timestamp] $message"
|
}
|
||||||
if (_logs.value.size > 50) {
|
|
||||||
_logs.value = _logs.value.takeLast(50)
|
private fun logError(message: String) {
|
||||||
}
|
Log.e("MindMachineBilling", message)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val billingClient: BillingClient = BillingClient.newBuilder(appContext)
|
private val billingClient: BillingClient = BillingClient.newBuilder(appContext)
|
||||||
@@ -64,12 +59,15 @@ class BillingManager(
|
|||||||
log("Billing setup finished: ${result.responseCode} ${result.debugMessage}")
|
log("Billing setup finished: ${result.responseCode} ${result.debugMessage}")
|
||||||
if (result.responseCode == BillingClient.BillingResponseCode.OK) {
|
if (result.responseCode == BillingClient.BillingResponseCode.OK) {
|
||||||
refresh()
|
refresh()
|
||||||
|
} else {
|
||||||
|
logError("Billing setup failed: ${result.responseCode} ${result.debugMessage}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBillingServiceDisconnected() {
|
override fun onBillingServiceDisconnected() {
|
||||||
log("Billing service disconnected")
|
log("Billing service disconnected - attempting reconnect")
|
||||||
// No-op, will retry on next call.
|
// Auto-reconnection handled by billing client, retry connection
|
||||||
|
start()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -120,7 +118,11 @@ class BillingManager(
|
|||||||
.build()
|
.build()
|
||||||
|
|
||||||
val result = billingClient.launchBillingFlow(activity, params)
|
val result = billingClient.launchBillingFlow(activity, params)
|
||||||
log("launchBillingFlow result: ${result.responseCode} ${result.debugMessage}")
|
if (result.responseCode == BillingClient.BillingResponseCode.OK) {
|
||||||
|
log("launchBillingFlow succeeded")
|
||||||
|
} else {
|
||||||
|
logError("launchBillingFlow failed: ${result.responseCode} ${result.debugMessage}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun queryProductDetails() {
|
private fun queryProductDetails() {
|
||||||
@@ -146,10 +148,11 @@ class BillingManager(
|
|||||||
.build()
|
.build()
|
||||||
|
|
||||||
billingClient.queryProductDetailsAsync(params, ProductDetailsResponseListener { result, detailsList ->
|
billingClient.queryProductDetailsAsync(params, ProductDetailsResponseListener { result, detailsList ->
|
||||||
log("queryProductDetails result: ${result.responseCode} ${result.debugMessage}")
|
|
||||||
if (result.responseCode == BillingClient.BillingResponseCode.OK) {
|
if (result.responseCode == BillingClient.BillingResponseCode.OK) {
|
||||||
_productDetails.value = detailsList.associateBy { it.productId }
|
|
||||||
log("Product details loaded: ${detailsList.size} items")
|
log("Product details loaded: ${detailsList.size} items")
|
||||||
|
_productDetails.value = detailsList.associateBy { it.productId }
|
||||||
|
} else {
|
||||||
|
logError("queryProductDetails failed: ${result.responseCode} ${result.debugMessage}")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -162,9 +165,11 @@ class BillingManager(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val listener = PurchasesResponseListener { result, purchases ->
|
val listener = PurchasesResponseListener { result, purchases ->
|
||||||
log("Query purchases result: ${result.responseCode} ${result.debugMessage}")
|
|
||||||
if (result.responseCode == BillingClient.BillingResponseCode.OK) {
|
if (result.responseCode == BillingClient.BillingResponseCode.OK) {
|
||||||
|
log("Query purchases returned ${purchases.size} items")
|
||||||
handlePurchases(purchases)
|
handlePurchases(purchases)
|
||||||
|
} else {
|
||||||
|
logError("Query purchases failed: ${result.responseCode} ${result.debugMessage}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,7 +184,7 @@ class BillingManager(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun handlePurchases(purchases: List<Purchase>) {
|
private fun handlePurchases(purchases: List<Purchase>) {
|
||||||
log("handlePurchases() count=${purchases.size}")
|
log("handlePurchases count=${purchases.size}")
|
||||||
val active = purchases.any { p ->
|
val active = purchases.any { p ->
|
||||||
p.purchaseState == Purchase.PurchaseState.PURCHASED &&
|
p.purchaseState == Purchase.PurchaseState.PURCHASED &&
|
||||||
p.products.any { it == BillingProducts.SUB_REMOVE_ADS_MONTHLY || it == BillingProducts.INAPP_REMOVE_ADS_FOREVER }
|
p.products.any { it == BillingProducts.SUB_REMOVE_ADS_MONTHLY || it == BillingProducts.INAPP_REMOVE_ADS_FOREVER }
|
||||||
@@ -199,7 +204,13 @@ class BillingManager(
|
|||||||
val params = AcknowledgePurchaseParams.newBuilder()
|
val params = AcknowledgePurchaseParams.newBuilder()
|
||||||
.setPurchaseToken(purchase.purchaseToken)
|
.setPurchaseToken(purchase.purchaseToken)
|
||||||
.build()
|
.build()
|
||||||
billingClient.acknowledgePurchase(params) { /* no-op */ }
|
billingClient.acknowledgePurchase(params) { billingResult ->
|
||||||
|
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
|
||||||
|
log("Purchase acknowledged successfully")
|
||||||
|
} else {
|
||||||
|
logError("Failed to acknowledge purchase: ${billingResult.responseCode} ${billingResult.debugMessage}")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user