1+ package com.blurr.voice
2+
3+ import android.os.Bundle
4+ import android.util.Log
5+ import android.view.View
6+ import android.widget.Button
7+ import android.widget.ProgressBar
8+ import android.widget.TextView
9+ import android.widget.Toast
10+ import androidx.appcompat.app.AppCompatActivity
11+ import androidx.lifecycle.lifecycleScope
12+ import com.android.billingclient.api.*
13+ import com.blurr.voice.MyApplication
14+ import kotlinx.coroutines.flow.first
15+ import kotlinx.coroutines.launch
16+ import kotlinx.coroutines.withTimeoutOrNull
17+
18+ class ProPurchaseActivity : AppCompatActivity (), PurchasesUpdatedListener {
19+
20+ private lateinit var priceTextView: TextView
21+ private lateinit var purchaseButton: Button
22+ private lateinit var loadingProgressBar: ProgressBar
23+ private lateinit var featuresTextView: TextView
24+ private lateinit var backButton: View
25+
26+ private val billingClient: BillingClient = MyApplication .billingClient
27+ private var productDetails: ProductDetails ? = null
28+
29+ companion object {
30+ private const val PRO_SKU = " panda_premium_monthly"
31+ private const val TAG = " ProPurchaseActivity"
32+ }
33+
34+ override fun onCreate (savedInstanceState : Bundle ? ) {
35+ super .onCreate(savedInstanceState)
36+ setContentView(R .layout.activity_pro_purchase)
37+
38+ initializeViews()
39+ setupClickListeners()
40+ loadProductDetails()
41+ }
42+
43+ private fun initializeViews () {
44+ priceTextView = findViewById(R .id.price_text)
45+ purchaseButton = findViewById(R .id.purchase_button)
46+ loadingProgressBar = findViewById(R .id.loading_progress)
47+ // featuresTextView = findViewById(R.id.features_text)
48+ backButton = findViewById(R .id.back_button)
49+
50+ // Initially hide purchase button and show loading
51+ purchaseButton.visibility = View .GONE
52+ loadingProgressBar.visibility = View .VISIBLE
53+ }
54+
55+ private fun setupClickListeners () {
56+ backButton.setOnClickListener {
57+ finish()
58+ }
59+
60+ purchaseButton.setOnClickListener {
61+ launchPurchaseFlow()
62+ }
63+ }
64+
65+ private fun loadProductDetails () {
66+ lifecycleScope.launch {
67+ try {
68+ // Wait for billing client to be ready
69+ val isReady = withTimeoutOrNull(10000L ) {
70+ MyApplication .isBillingClientReady.first { it }
71+ }
72+
73+ if (isReady != true ) {
74+ showError(" Unable to connect to Play Store. Please try again later." )
75+ return @launch
76+ }
77+
78+ val productList = listOf (
79+ QueryProductDetailsParams .Product .newBuilder()
80+ .setProductId(PRO_SKU )
81+ .setProductType(BillingClient .ProductType .SUBS )
82+ .build()
83+ )
84+
85+ val params = QueryProductDetailsParams .newBuilder()
86+ .setProductList(productList)
87+ .build()
88+
89+ val productDetailsResult = billingClient.queryProductDetails(params)
90+
91+ if (productDetailsResult.billingResult.responseCode == BillingClient .BillingResponseCode .OK ) {
92+ val productDetailsList = productDetailsResult.productDetailsList
93+ Log .d(TAG , " Product details: $productDetailsList " )
94+ if (productDetailsList?.isNotEmpty() == true ) {
95+ productDetails = productDetailsList[0 ]
96+ updateUIWithProductDetails()
97+ } else {
98+ showError(" Pro subscription not available" )
99+ }
100+ } else {
101+ showError(" Failed to load pricing information" )
102+ }
103+
104+ } catch (e: Exception ) {
105+ Log .e(TAG , " Error loading product details" , e)
106+ showError(" Error loading pricing information" )
107+ }
108+ }
109+ }
110+
111+ private fun updateUIWithProductDetails () {
112+ productDetails?.let { details ->
113+ val subscriptionOfferDetails = details.subscriptionOfferDetails?.firstOrNull()
114+ val pricingPhase = subscriptionOfferDetails?.pricingPhases?.pricingPhaseList?.firstOrNull()
115+
116+ if (pricingPhase != null ) {
117+ val formattedPrice = pricingPhase.formattedPrice
118+ val billingPeriod = pricingPhase.billingPeriod
119+
120+ // Convert billing period to readable format
121+ val periodText = when {
122+ billingPeriod.contains(" P1M" ) -> " month"
123+ billingPeriod.contains(" P1Y" ) -> " year"
124+ billingPeriod.contains(" P1W" ) -> " week"
125+ else -> " billing period"
126+ }
127+
128+ priceTextView.text = " $formattedPrice /$periodText "
129+
130+ // Show purchase button and hide loading
131+ loadingProgressBar.visibility = View .GONE
132+ purchaseButton.visibility = View .VISIBLE
133+
134+ } else {
135+ showError(" Pricing information not available" )
136+ }
137+ }
138+ }
139+
140+ private fun launchPurchaseFlow () {
141+ productDetails?.let { details ->
142+ val subscriptionOfferDetails = details.subscriptionOfferDetails?.firstOrNull()
143+ if (subscriptionOfferDetails != null ) {
144+ val productDetailsParamsList = listOf (
145+ BillingFlowParams .ProductDetailsParams .newBuilder()
146+ .setProductDetails(details)
147+ .setOfferToken(subscriptionOfferDetails.offerToken)
148+ .build()
149+ )
150+
151+ val billingFlowParams = BillingFlowParams .newBuilder()
152+ .setProductDetailsParamsList(productDetailsParamsList)
153+ .build()
154+
155+ val billingResult = billingClient.launchBillingFlow(this , billingFlowParams)
156+
157+ if (billingResult.responseCode != BillingClient .BillingResponseCode .OK ) {
158+ Toast .makeText(this , " Failed to launch purchase flow" , Toast .LENGTH_SHORT ).show()
159+ }
160+ }
161+ }
162+ }
163+
164+ private fun showError (message : String ) {
165+ loadingProgressBar.visibility = View .GONE
166+ priceTextView.text = " Pricing unavailable"
167+ purchaseButton.visibility = View .GONE
168+ Toast .makeText(this , message, Toast .LENGTH_LONG ).show()
169+ }
170+
171+ override fun onPurchasesUpdated (billingResult : BillingResult , purchases : MutableList <Purchase >? ) {
172+ if (billingResult.responseCode == BillingClient .BillingResponseCode .OK && purchases != null ) {
173+ for (purchase in purchases) {
174+ if (purchase.purchaseState == Purchase .PurchaseState .PURCHASED ) {
175+ Toast .makeText(this , " Purchase successful! Welcome to Pro!" , Toast .LENGTH_LONG ).show()
176+ finish() // Close the purchase activity
177+ }
178+ }
179+ } else if (billingResult.responseCode == BillingClient .BillingResponseCode .USER_CANCELED ) {
180+ Toast .makeText(this , " Purchase cancelled" , Toast .LENGTH_SHORT ).show()
181+ } else {
182+ Toast .makeText(this , " Purchase failed: ${billingResult.debugMessage} " , Toast .LENGTH_LONG ).show()
183+ }
184+ }
185+ }
0 commit comments