mirror of
https://github.com/samuelclay/NewsBlur.git
synced 2025-09-18 21:50:56 +00:00
Upgrade subscriptions to billing 4.0
This commit is contained in:
parent
2b5509c994
commit
e0c6f96008
3 changed files with 88 additions and 52 deletions
|
@ -32,7 +32,7 @@ dependencies {
|
|||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
||||
implementation 'com.squareup.okhttp3:okhttp:4.9.2'
|
||||
implementation 'com.google.code.gson:gson:2.8.6'
|
||||
implementation 'com.android.billingclient:billing:3.0.3'
|
||||
implementation 'com.android.billingclient:billing:4.0.0'
|
||||
implementation 'nl.dionsegijn:konfetti:1.2.2'
|
||||
implementation 'com.google.android.play:core:1.10.2'
|
||||
implementation "com.google.android.material:material:1.4.0"
|
||||
|
|
|
@ -7,6 +7,7 @@ import android.os.Bundle
|
|||
import android.text.util.Linkify
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.android.billingclient.api.*
|
||||
import com.newsblur.R
|
||||
import com.newsblur.databinding.ActivityPremiumBinding
|
||||
|
@ -36,7 +37,7 @@ class Premium : NbActivity() {
|
|||
}
|
||||
|
||||
override fun onBillingConnectionReady() {
|
||||
subscriptionManager.getSubscriptionState()
|
||||
subscriptionManager.syncSubscriptionState()
|
||||
}
|
||||
|
||||
override fun onBillingConnectionError(message: String?) {
|
||||
|
@ -67,8 +68,8 @@ class Premium : NbActivity() {
|
|||
}
|
||||
|
||||
private fun setupBilling() {
|
||||
subscriptionManager = SubscriptionManagerImpl(this, subscriptionManagerListener)
|
||||
subscriptionManager.startBillingConnection()
|
||||
subscriptionManager = SubscriptionManagerImpl(this, lifecycleScope)
|
||||
subscriptionManager.startBillingConnection(subscriptionManagerListener)
|
||||
}
|
||||
|
||||
private fun showSubscriptionDetailsError(message: String?) {
|
||||
|
|
|
@ -21,6 +21,12 @@ import com.newsblur.util.Log
|
|||
import com.newsblur.util.NBScope
|
||||
import com.newsblur.util.PrefsUtils
|
||||
import com.newsblur.util.executeAsyncTask
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Deferred
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.text.DateFormat
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
@ -31,7 +37,7 @@ interface SubscriptionManager {
|
|||
* Open connection to Play Store to retrieve
|
||||
* purchases and subscriptions.
|
||||
*/
|
||||
fun startBillingConnection()
|
||||
fun startBillingConnection(listener: SubscriptionsListener? = null)
|
||||
|
||||
/**
|
||||
* Generated subscription state by retrieve all available subscriptions
|
||||
|
@ -39,7 +45,7 @@ interface SubscriptionManager {
|
|||
*
|
||||
* Subscriptions are configured via the Play Store console.
|
||||
*/
|
||||
fun getSubscriptionState()
|
||||
fun syncSubscriptionState()
|
||||
|
||||
/**
|
||||
* Launch the billing flow overlay for a specific subscription.
|
||||
|
@ -53,7 +59,12 @@ interface SubscriptionManager {
|
|||
*/
|
||||
fun syncActiveSubscription()
|
||||
|
||||
fun hasActiveSubscription(): Boolean
|
||||
/**
|
||||
* Notify backend of active Play Store subscription.
|
||||
*/
|
||||
fun saveReceipt(purchase: Purchase)
|
||||
|
||||
suspend fun hasActiveSubscription(): Boolean
|
||||
}
|
||||
|
||||
interface SubscriptionsListener {
|
||||
|
@ -69,9 +80,11 @@ interface SubscriptionsListener {
|
|||
|
||||
class SubscriptionManagerImpl(
|
||||
private val context: Context,
|
||||
private val listener: SubscriptionsListener
|
||||
private val scope: CoroutineScope = NBScope,
|
||||
) : SubscriptionManager {
|
||||
|
||||
private var listener: SubscriptionsListener? = null
|
||||
|
||||
private val acknowledgePurchaseListener = AcknowledgePurchaseResponseListener { billingResult: BillingResult ->
|
||||
when (billingResult.responseCode) {
|
||||
BillingClient.BillingResponseCode.OK -> {
|
||||
|
@ -121,9 +134,9 @@ class SubscriptionManagerImpl(
|
|||
override fun onBillingSetupFinished(billingResult: BillingResult) {
|
||||
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
|
||||
Log.d(this, "onBillingSetupFinished OK")
|
||||
listener.onBillingConnectionReady()
|
||||
listener?.onBillingConnectionReady()
|
||||
} else {
|
||||
listener.onBillingConnectionError("Error connecting to Play Store.")
|
||||
listener?.onBillingConnectionError("Error connecting to Play Store.")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -131,7 +144,7 @@ class SubscriptionManagerImpl(
|
|||
Log.d(this, "onBillingServiceDisconnected")
|
||||
// Try to restart the connection on the next request to
|
||||
// Google Play by calling the startConnection() method.
|
||||
listener.onBillingConnectionError("Error connecting to Play Store.")
|
||||
listener?.onBillingConnectionError("Error connecting to Play Store.")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -140,13 +153,17 @@ class SubscriptionManagerImpl(
|
|||
.enablePendingPurchases()
|
||||
.build()
|
||||
|
||||
override fun startBillingConnection() {
|
||||
override fun startBillingConnection(listener: SubscriptionsListener?) {
|
||||
this.listener = listener
|
||||
billingClient.startConnection(billingClientStateListener)
|
||||
}
|
||||
|
||||
override fun getSubscriptionState() =
|
||||
override fun syncSubscriptionState() {
|
||||
scope.launch {
|
||||
if (hasActiveSubscription()) syncActiveSubscription()
|
||||
else syncAvailableSubscription()
|
||||
}
|
||||
}
|
||||
|
||||
override fun purchaseSubscription(activity: Activity, skuDetails: SkuDetails) {
|
||||
Log.d(this, "launchBillingFlow for sku: ${skuDetails.sku}")
|
||||
|
@ -157,30 +174,56 @@ class SubscriptionManagerImpl(
|
|||
}
|
||||
|
||||
override fun syncActiveSubscription() {
|
||||
val hasNewsBlurSubscription = PrefsUtils.getIsPremium(context)
|
||||
val activePlayStoreSubscription = getActivePlayStoreSubscription()
|
||||
scope.launch(Dispatchers.Default) {
|
||||
val hasNewsBlurSubscription = PrefsUtils.getIsPremium(context)
|
||||
val activePlayStoreSubscription = getActiveSubscriptionAsync().await()
|
||||
|
||||
if (hasNewsBlurSubscription || activePlayStoreSubscription != null) {
|
||||
val renewalString: String? = getRenewalMessage(activePlayStoreSubscription)
|
||||
listener.onActiveSubscription(renewalString)
|
||||
}
|
||||
if (hasNewsBlurSubscription || activePlayStoreSubscription != null) {
|
||||
val renewalString: String? = getRenewalMessage(activePlayStoreSubscription)
|
||||
withContext(Dispatchers.Main) {
|
||||
listener?.onActiveSubscription(renewalString)
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasNewsBlurSubscription && activePlayStoreSubscription != null) {
|
||||
saveSubscriptionReceipt(activePlayStoreSubscription)
|
||||
if (!hasNewsBlurSubscription && activePlayStoreSubscription != null) {
|
||||
saveReceipt(activePlayStoreSubscription)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun hasActiveSubscription(): Boolean =
|
||||
PrefsUtils.getIsPremium(context) || getActivePlayStoreSubscription() != null
|
||||
override suspend fun hasActiveSubscription(): Boolean =
|
||||
PrefsUtils.getIsPremium(context) || getActiveSubscriptionAsync().await() != null
|
||||
|
||||
private fun getActivePlayStoreSubscription(): Purchase? {
|
||||
val result = billingClient.queryPurchases(BillingClient.SkuType.SUBS)
|
||||
return result.purchasesList?.let {
|
||||
it.find { purchase -> purchase.sku == AppConstants.PREMIUM_SKU }
|
||||
override fun saveReceipt(purchase: Purchase) {
|
||||
Log.d(this, "saveReceipt: ${purchase.orderId}")
|
||||
val apiManager = APIManager(context)
|
||||
scope.executeAsyncTask(
|
||||
doInBackground = {
|
||||
apiManager.saveReceipt(purchase.orderId, purchase.skus.first())
|
||||
},
|
||||
onPostExecute = {
|
||||
if (!it.isError) {
|
||||
NBSyncService.forceFeedsFolders()
|
||||
FeedUtils.triggerSync(context)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun syncAvailableSubscription() {
|
||||
scope.launch(Dispatchers.Default) {
|
||||
val skuDetails = getAvailableSubscriptionAsync().await()
|
||||
withContext(Dispatchers.Main) {
|
||||
skuDetails?.let {
|
||||
Log.d(this, it.toString())
|
||||
listener?.onAvailableSubscription(it)
|
||||
} ?: listener?.onBillingConnectionError()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun syncAvailableSubscription() {
|
||||
private fun getAvailableSubscriptionAsync(): Deferred<SkuDetails?> {
|
||||
val deferred = CompletableDeferred<SkuDetails?>()
|
||||
val params = SkuDetailsParams.newBuilder().apply {
|
||||
// add subscription SKUs from Play Store
|
||||
setSkusList(listOf(AppConstants.PREMIUM_SKU))
|
||||
|
@ -191,16 +234,26 @@ class SubscriptionManagerImpl(
|
|||
Log.d(this, "SkuDetailsResponse ${skuDetailsList.toString()}")
|
||||
skuDetailsList?.let {
|
||||
// Currently interested only in the premium yearly News Blur subscription.
|
||||
val premiumSubscription = it.find { skuDetails ->
|
||||
val skuDetails = it.find { skuDetails ->
|
||||
skuDetails.sku == AppConstants.PREMIUM_SKU
|
||||
}
|
||||
|
||||
premiumSubscription?.let { skuDetail ->
|
||||
Log.d(this, skuDetail.toString())
|
||||
listener.onAvailableSubscription(skuDetail)
|
||||
} ?: listener.onBillingConnectionError()
|
||||
}
|
||||
Log.d(this, skuDetails.toString())
|
||||
deferred.complete(skuDetails)
|
||||
} ?: deferred.complete(null)
|
||||
}
|
||||
|
||||
return deferred
|
||||
}
|
||||
|
||||
private fun getActiveSubscriptionAsync(): Deferred<Purchase?> {
|
||||
val deferred = CompletableDeferred<Purchase?>()
|
||||
billingClient.queryPurchasesAsync(BillingClient.SkuType.SUBS) { _, purchases ->
|
||||
val purchase = purchases.find { purchase -> purchase.skus.contains(AppConstants.PREMIUM_SKU) }
|
||||
deferred.complete(purchase)
|
||||
}
|
||||
|
||||
return deferred
|
||||
}
|
||||
|
||||
private fun handlePurchase(purchase: Purchase) {
|
||||
|
@ -219,24 +272,6 @@ class SubscriptionManagerImpl(
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify backend of active Play Store subscription.
|
||||
*/
|
||||
private fun saveSubscriptionReceipt(purchase: Purchase) {
|
||||
val apiManager = APIManager(context)
|
||||
NBScope.executeAsyncTask(
|
||||
doInBackground = {
|
||||
apiManager.saveReceipt(purchase.orderId, purchase.sku)
|
||||
},
|
||||
onPostExecute = {
|
||||
if (!it.isError) {
|
||||
NBSyncService.forceFeedsFolders()
|
||||
FeedUtils.triggerSync(context)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate subscription renewal message.
|
||||
*/
|
||||
|
|
Loading…
Add table
Reference in a new issue