diff --git a/app/build.gradle b/app/build.gradle
index c54b385d..8ee8c89d 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -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'
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 8c2989ed..123dffcf 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -4,6 +4,7 @@
+
+
+
diff --git a/app/src/main/java/net/pokeranalytics/android/PokerAnalyticsApplication.kt b/app/src/main/java/net/pokeranalytics/android/PokerAnalyticsApplication.kt
index 33b16f46..5ca7c7f0 100644
--- a/app/src/main/java/net/pokeranalytics/android/PokerAnalyticsApplication.kt
+++ b/app/src/main/java/net/pokeranalytics/android/PokerAnalyticsApplication.kt
@@ -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()
diff --git a/app/src/main/java/net/pokeranalytics/android/ui/activity/BillingActivity.kt b/app/src/main/java/net/pokeranalytics/android/ui/activity/BillingActivity.kt
new file mode 100644
index 00000000..9a313130
--- /dev/null
+++ b/app/src/main/java/net/pokeranalytics/android/ui/activity/BillingActivity.kt
@@ -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()
+ }
+
+}
+
+
diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/SubscriptionFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/SubscriptionFragment.kt
new file mode 100644
index 00000000..b7a8cdcc
--- /dev/null
+++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/SubscriptionFragment.kt
@@ -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?) {
+
+ 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)
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/net/pokeranalytics/android/util/billing/AppGuard.kt b/app/src/main/java/net/pokeranalytics/android/util/billing/AppGuard.kt
new file mode 100644
index 00000000..4d1e16d4
--- /dev/null
+++ b/app/src/main/java/net/pokeranalytics/android/util/billing/AppGuard.kt
@@ -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()
+ 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?) {
+
+ 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
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_billing.xml b/app/src/main/res/layout/activity_billing.xml
new file mode 100644
index 00000000..08d1f651
--- /dev/null
+++ b/app/src/main/res/layout/activity_billing.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_subscription.xml b/app/src/main/res/layout/fragment_subscription.xml
new file mode 100644
index 00000000..90b15285
--- /dev/null
+++ b/app/src/main/res/layout/fragment_subscription.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 73666f07..26f159bb 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -11,6 +11,7 @@
Initial Value
Can\'t show because there is less than two values to display!
The object you\'re trying to access is invalid
+ The billing service is unavailable at the moment. Please check your internet connection and retry later.
Address
Naming suggestions