first draft of IAP implementation

dev
Laurent 7 years ago
parent 7d86df39fd
commit 15b2c6b3d6
  1. 3
      app/build.gradle
  2. 6
      app/src/main/AndroidManifest.xml
  3. 5
      app/src/main/java/net/pokeranalytics/android/PokerAnalyticsApplication.kt
  4. 36
      app/src/main/java/net/pokeranalytics/android/ui/activity/BillingActivity.kt
  5. 68
      app/src/main/java/net/pokeranalytics/android/ui/fragment/SubscriptionFragment.kt
  6. 110
      app/src/main/java/net/pokeranalytics/android/util/billing/AppGuard.kt
  7. 15
      app/src/main/res/layout/activity_billing.xml
  8. 19
      app/src/main/res/layout/fragment_subscription.xml
  9. 1
      app/src/main/res/values/strings.xml

@ -87,6 +87,9 @@ dependencies {
// Places
implementation 'com.google.android.libraries.places:places:1.1.0'
// Billing / Subscriptions
implementation 'com.android.billingclient:billing:1.2.2'
// Firebase
implementation 'com.google.firebase:firebase-core:16.0.8'

@ -4,6 +4,7 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="com.android.vending.BILLING" />
<application
android:name=".PokerAnalyticsApplication"
@ -95,6 +96,11 @@
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity
android:name="net.pokeranalytics.android.ui.activity.BillingActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<meta-data
android:name="preloaded_fonts"
android:resource="@array/preloaded_fonts" />

@ -26,6 +26,9 @@ class PokerAnalyticsApplication : Application() {
super.onCreate()
UserDefaults.init(this)
// AppGuard / Billing services
// AppGuard.load(this.applicationContext)
// Realm
Realm.init(this)
val realmConfiguration = RealmConfiguration.Builder()
@ -52,7 +55,7 @@ class PokerAnalyticsApplication : Application() {
if (BuildConfig.DEBUG) {
Timber.d("UserPreferences.defaultCurrency: ${UserDefaults.currency.symbol}")
this.createFakeSessions()
// this.createFakeSessions()
}
Patcher.patchBreaks()

@ -0,0 +1,36 @@
package net.pokeranalytics.android.ui.activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import net.pokeranalytics.android.R
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity
import net.pokeranalytics.android.util.billing.AppGuard
enum class IAPProducts(var identifier: String) {
PRO("pro")
}
class BillingActivity : PokerAnalyticsActivity() {
companion object {
fun newInstance(context: Context) {
val intent = Intent(context, BillingActivity::class.java)
context.startActivity(intent)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_billing)
}
override fun onResume() {
super.onResume()
AppGuard.restorePurchases()
}
}

@ -0,0 +1,68 @@
package net.pokeranalytics.android.ui.fragment
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import com.android.billingclient.api.BillingClient
import com.android.billingclient.api.SkuDetails
import com.android.billingclient.api.SkuDetailsResponseListener
import kotlinx.android.synthetic.main.fragment_subscription.*
import net.pokeranalytics.android.R
import net.pokeranalytics.android.ui.activity.IAPProducts
import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment
import net.pokeranalytics.android.util.billing.AppGuard
class SubscriptionFragment : PokerAnalyticsFragment(), SkuDetailsResponseListener {
var selectedProduct: SkuDetails? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (!AppGuard.requestProducts(this)) {
Toast.makeText(requireContext(), R.string.billingclient_unavailable, Toast.LENGTH_LONG).show()
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_subscription, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initUI()
}
// SkuDetailsResponseListener
override fun onSkuDetailsResponse(responseCode: Int, skuDetailsList: MutableList<SkuDetails>?) {
if (responseCode == BillingClient.BillingResponse.OK && skuDetailsList != null) {
selectedProduct = skuDetailsList.first { it.sku == IAPProducts.PRO.identifier }
updatePurchaseButtonState()
}
}
private fun initUI() {
this.purchase.setOnClickListener {
this.selectedProduct?.let {
AppGuard.initiatePurchase(this.requireActivity(), it)
} ?: run {
throw IllegalStateException("Attempt to initiate purchase while no product has been chosen")
}
}
}
private fun updatePurchaseButtonState() {
this.purchase.isEnabled = (this.selectedProduct != null)
}
}

@ -0,0 +1,110 @@
package net.pokeranalytics.android.util.billing
import android.app.Activity
import android.content.Context
import com.android.billingclient.api.*
import net.pokeranalytics.android.ui.activity.IAPProducts
object AppGuard : PurchasesUpdatedListener {
lateinit private var billingClient: BillingClient
private var billingClientAvailable: Boolean = false
fun load(context: Context) {
billingClient = BillingClient.newBuilder(context).setListener(this).build()
this.restorePurchases()
billingClient.startConnection(object : BillingClientStateListener {
override fun onBillingSetupFinished(@BillingClient.BillingResponse billingResponseCode: Int) {
if (billingResponseCode == BillingClient.BillingResponse.OK) {
// The BillingClient is ready. You can query purchases here.
billingClientAvailable = true
}
}
override fun onBillingServiceDisconnected() {
billingClientAvailable = false
// Try to restart the connection on the next request to
// Google Play by calling the startConnection() method.
}
})
}
fun restorePurchases() {
// Automatically checks for purchases (when switching devices for example)
val purchasesResult=
billingClient.queryPurchases(BillingClient.SkuType.SUBS)
purchasesResult.purchasesList.forEach {
this.handlePurchase(it)
}
}
fun requestProducts(listener: SkuDetailsResponseListener) : Boolean {
if (this.billingClientAvailable) {
val skuList = ArrayList<String>()
skuList.add(IAPProducts.PRO.identifier)
val params = SkuDetailsParams.newBuilder()
params.setSkusList(skuList).setType(BillingClient.SkuType.SUBS)
billingClient.querySkuDetailsAsync(params.build(), listener)
return true
}
return false
}
fun initiatePurchase(activity: Activity, skuDetails: SkuDetails) {
val flowParams = BillingFlowParams.newBuilder()
.setSkuDetails(skuDetails)
.build()
val responseCode = billingClient.launchBillingFlow(activity, flowParams)
}
// PurchasesUpdatedListener
override fun onPurchasesUpdated(responseCode: Int, purchases: MutableList<Purchase>?) {
if (responseCode == BillingClient.BillingResponse.OK && purchases != null) {
for (purchase in purchases) {
handlePurchase(purchase)
}
} else if (responseCode == BillingClient.BillingResponse.USER_CANCELED) {
// Handle an error caused by a user cancelling the purchase flow.
} else {
// Handle any other error codes.
}
}
private fun handlePurchase(purchase: Purchase) {
val token = purchase.purchaseToken
val oj = purchase.originalJson
if (this.verifyPurchase(purchase)) {
when (purchase.sku) {
IAPProducts.PRO.identifier -> {
}
else -> {}
}
} else {
// invalid purchase
}
}
private fun verifyPurchase(purchase: Purchase) : Boolean {
return true
}
}

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical">
<fragment
android:id="@+id/subscriptionFragment"
android:name="net.pokeranalytics.android.ui.fragment.SubscriptionFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:layout="@layout/fragment_subscription" />
</LinearLayout>

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.google.android.material.button.MaterialButton
android:id="@+id/purchase"
style="@style/PokerAnalyticsTheme.Button"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:text="@string/purchase" />
</androidx.constraintlayout.widget.ConstraintLayout>

@ -11,6 +11,7 @@
<string name="initial_value">Initial Value</string>
<string name="less_then_2_values_for_display">Can\'t show because there is less than two values to display!</string>
<string name="invalid_object">The object you\'re trying to access is invalid</string>
<string name="billingclient_unavailable">The billing service is unavailable at the moment. Please check your internet connection and retry later.</string>
<string name="address">Address</string>
<string name="suggestions">Naming suggestions</string>

Loading…
Cancel
Save