Phase 6: Add Android subscription UI with Jetpack Compose
Implemented Android subscription UI components: - UpgradeFeatureScreen: Full-screen view for restricted features (contractors, documents) * Shows feature icon, name, and description * Displays "This feature is available with Pro" badge * Opens UpgradePromptDialog on button click - UpgradePromptDialog: Modal dialog with upgrade options * Trigger-specific title and message from backend * Feature preview list with Material icons * "Upgrade to Pro" button with loading state * "Compare Free vs Pro" button * "Maybe Later" cancel option - FeatureComparisonDialog: Full-screen comparison table * Free vs Pro feature comparison * Displays data from SubscriptionCache.featureBenefits * Default features if no data loaded * Upgrade button - BillingManager: Google Play Billing Library placeholder * Singleton manager for in-app purchases * Product query placeholder * Purchase flow placeholder * Backend receipt verification placeholder * Restore purchases placeholder All components follow Material3 design system with theme-aware colors and spacing constants. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
package com.example.mycrib.platform
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
/**
|
||||
* Google Play Billing manager for in-app purchases
|
||||
* NOTE: Requires Google Play Console configuration and product IDs
|
||||
*/
|
||||
class BillingManager private constructor(context: Context) {
|
||||
|
||||
companion object {
|
||||
@Volatile
|
||||
private var INSTANCE: BillingManager? = null
|
||||
|
||||
fun getInstance(context: Context): BillingManager {
|
||||
return INSTANCE ?: synchronized(this) {
|
||||
INSTANCE ?: BillingManager(context.applicationContext).also { INSTANCE = it }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Product ID for Pro subscription (configure in Google Play Console)
|
||||
private val proSubscriptionProductID = "com.example.mycrib.pro.monthly"
|
||||
|
||||
private val _isLoading = MutableStateFlow(false)
|
||||
val isLoading: StateFlow<Boolean> = _isLoading
|
||||
|
||||
private val _purchasedProductIDs = MutableStateFlow<Set<String>>(emptySet())
|
||||
val purchasedProductIDs: StateFlow<Set<String>> = _purchasedProductIDs
|
||||
|
||||
init {
|
||||
// Start listening for purchases
|
||||
// In production, initialize BillingClient here
|
||||
println("BillingManager: Initialized")
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to Google Play Billing
|
||||
*/
|
||||
fun startConnection(onSuccess: () -> Unit, onError: (String) -> Unit) {
|
||||
// In production, this would connect to BillingClient
|
||||
// billingClient.startConnection(object : BillingClientStateListener {
|
||||
// override fun onBillingSetupFinished(billingResult: BillingResult) {
|
||||
// if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
|
||||
// onSuccess()
|
||||
// } else {
|
||||
// onError("Billing setup failed")
|
||||
// }
|
||||
// }
|
||||
// override fun onBillingServiceDisconnected() { /* Retry */ }
|
||||
// })
|
||||
|
||||
println("BillingManager: Would connect to Google Play Billing")
|
||||
onSuccess()
|
||||
}
|
||||
|
||||
/**
|
||||
* Query available products
|
||||
*/
|
||||
suspend fun queryProducts(): List<ProductDetails> {
|
||||
_isLoading.value = true
|
||||
|
||||
try {
|
||||
// In production, this would query real products
|
||||
// val params = QueryProductDetailsParams.newBuilder()
|
||||
// .setProductList(listOf(
|
||||
// Product.newBuilder()
|
||||
// .setProductId(proSubscriptionProductID)
|
||||
// .setProductType(BillingClient.ProductType.SUBS)
|
||||
// .build()
|
||||
// ))
|
||||
// .build()
|
||||
// val result = billingClient.queryProductDetails(params)
|
||||
|
||||
println("BillingManager: Would query products here")
|
||||
return emptyList()
|
||||
} finally {
|
||||
_isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch purchase flow
|
||||
*/
|
||||
fun launchPurchaseFlow(activity: Activity, productId: String, onSuccess: () -> Unit, onError: (String) -> Unit) {
|
||||
// In production, this would launch the purchase UI
|
||||
// val params = BillingFlowParams.newBuilder()
|
||||
// .setProductDetailsParamsList(listOf(productDetailsParams))
|
||||
// .build()
|
||||
// val billingResult = billingClient.launchBillingFlow(activity, params)
|
||||
|
||||
println("BillingManager: Would launch purchase flow for: $productId")
|
||||
onError("Purchase not implemented yet")
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify purchase with backend
|
||||
*/
|
||||
suspend fun verifyPurchaseWithBackend(purchaseToken: String, productId: String) {
|
||||
// TODO: Call backend API to verify purchase
|
||||
// val api = SubscriptionApi()
|
||||
// val result = api.verifyAndroidPurchase(
|
||||
// token = tokenStorage.getToken(),
|
||||
// purchaseToken = purchaseToken,
|
||||
// productId = productId
|
||||
// )
|
||||
|
||||
println("BillingManager: Would verify purchase with backend")
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore purchases
|
||||
*/
|
||||
suspend fun restorePurchases() {
|
||||
// In production, this would query purchase history
|
||||
// val result = billingClient.queryPurchasesAsync(
|
||||
// QueryPurchasesParams.newBuilder()
|
||||
// .setProductType(BillingClient.ProductType.SUBS)
|
||||
// .build()
|
||||
// )
|
||||
|
||||
println("BillingManager: Would restore purchases here")
|
||||
}
|
||||
|
||||
/**
|
||||
* Acknowledge purchase (required by Google Play)
|
||||
*/
|
||||
private suspend fun acknowledgePurchase(purchaseToken: String) {
|
||||
// In production, this would acknowledge the purchase
|
||||
// val params = AcknowledgePurchaseParams.newBuilder()
|
||||
// .setPurchaseToken(purchaseToken)
|
||||
// .build()
|
||||
// billingClient.acknowledgePurchase(params)
|
||||
|
||||
println("BillingManager: Would acknowledge purchase")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Placeholder for ProductDetails
|
||||
* In production, use com.android.billingclient.api.ProductDetails
|
||||
*/
|
||||
data class ProductDetails(
|
||||
val productId: String,
|
||||
val title: String,
|
||||
val description: String,
|
||||
val price: String
|
||||
)
|
||||
Reference in New Issue
Block a user