From 3958396ce6e5356198f4d93b0cef28b6195d2f60 Mon Sep 17 00:00:00 2001 From: Laurent Date: Wed, 8 May 2019 14:46:50 +0200 Subject: [PATCH] Subscriptions update --- .../android/ui/activity/BillingActivity.kt | 1 - .../android/ui/activity/HomeActivity.kt | 9 ++- .../android/ui/extensions/UIExtensions.kt | 3 +- .../android/ui/fragment/FeedFragment.kt | 2 +- .../ui/fragment/SubscriptionFragment.kt | 69 +++++++++++++++---- .../android/util/Preferences.kt | 2 +- .../android/util/billing/AppGuard.kt | 21 ++++-- .../main/res/layout/fragment_subscription.xml | 33 ++++++++- app/src/main/res/values/strings.xml | 4 +- app/src/main/res/values/styles.xml | 20 ++++++ 10 files changed, 132 insertions(+), 32 deletions(-) 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 index 359d490b..71aafcd9 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/activity/BillingActivity.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/activity/BillingActivity.kt @@ -24,7 +24,6 @@ class BillingActivity : PokerAnalyticsActivity() { override fun onResume() { super.onResume() - AppGuard.restorePurchases() } } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/activity/HomeActivity.kt b/app/src/main/java/net/pokeranalytics/android/ui/activity/HomeActivity.kt index ca43f1c4..8c9b6294 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/activity/HomeActivity.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/activity/HomeActivity.kt @@ -7,18 +7,16 @@ import android.os.Build import android.os.Bundle import android.view.Menu import android.view.MenuItem -import androidx.appcompat.app.AlertDialog import com.google.android.material.bottomnavigation.BottomNavigationView import io.realm.RealmResults import kotlinx.android.synthetic.main.activity_home.* -import kotlinx.android.synthetic.main.bottom_sheet_sum.view.* import net.pokeranalytics.android.BuildConfig import net.pokeranalytics.android.R import net.pokeranalytics.android.model.realm.Currency import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity import net.pokeranalytics.android.ui.adapter.HomePagerAdapter import net.pokeranalytics.android.ui.interfaces.FilterHandler -import timber.log.Timber +import net.pokeranalytics.android.util.billing.AppGuard class HomeActivity : PokerAnalyticsActivity() { @@ -68,6 +66,11 @@ class HomeActivity : PokerAnalyticsActivity() { return@OnNavigationItemSelectedListener true } + override fun onResume() { + super.onResume() + AppGuard.requestPurchasesUpdate() + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) diff --git a/app/src/main/java/net/pokeranalytics/android/ui/extensions/UIExtensions.kt b/app/src/main/java/net/pokeranalytics/android/ui/extensions/UIExtensions.kt index 10647782..e0c1e576 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/extensions/UIExtensions.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/extensions/UIExtensions.kt @@ -20,6 +20,7 @@ import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment import net.pokeranalytics.android.util.DeviceUtils import net.pokeranalytics.android.util.URL +import net.pokeranalytics.android.util.billing.AppGuard import java.io.File @@ -70,7 +71,7 @@ fun PokerAnalyticsActivity.openPlayStorePage() { // Open email for "Contact us" fun PokerAnalyticsActivity.openContactMail(subjectStringRes: Int, filePath: String?= null) { - val info = "v${BuildConfig.VERSION_NAME}(${BuildConfig.VERSION_CODE}), Android ${android.os.Build.VERSION.SDK_INT}, ${DeviceUtils.getDeviceName()}" + val info = "v${BuildConfig.VERSION_NAME}(${BuildConfig.VERSION_CODE}) - ${AppGuard.isProUser}, Android ${android.os.Build.VERSION.SDK_INT}, ${DeviceUtils.getDeviceName()}" val emailIntent = Intent(Intent.ACTION_SEND) diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/FeedFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/FeedFragment.kt index 5d167770..3bc6ecfc 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/FeedFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/FeedFragment.kt @@ -183,7 +183,7 @@ class FeedFragment : RealmFragment(), RowRepresentableDelegate { private fun createNewSession(isTournament: Boolean) { // if (!AppGuard.isProUser) { // && !BuildConfig.DEBUG -// Toast.makeText(context, "Please subscribe!", Toast.LENGTH_LONG).show() +//// Toast.makeText(context, "Please subscribe!", Toast.LENGTH_LONG).show() // BillingActivity.newInstance(requireContext()) // return // } 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 index e0f14895..0740ffb2 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/SubscriptionFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/SubscriptionFragment.kt @@ -1,23 +1,31 @@ package net.pokeranalytics.android.ui.fragment +import android.os.Build import android.os.Bundle +import android.text.SpannableStringBuilder +import android.text.Spanned +import android.text.style.TypefaceSpan import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.Toast +import androidx.core.content.res.ResourcesCompat import com.android.billingclient.api.BillingClient +import com.android.billingclient.api.Purchase 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.fragment.components.PokerAnalyticsFragment +import net.pokeranalytics.android.util.Preferences import net.pokeranalytics.android.util.billing.AppGuard import net.pokeranalytics.android.util.billing.IAPProducts import net.pokeranalytics.android.util.billing.PurchaseDelegate + class SubscriptionFragment : PokerAnalyticsFragment(), SkuDetailsResponseListener, PurchaseDelegate { - var selectedProduct: SkuDetails? = null + private var selectedProduct: SkuDetails? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -25,7 +33,6 @@ class SubscriptionFragment : PokerAnalyticsFragment(), SkuDetailsResponseListene 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? { @@ -36,21 +43,34 @@ class SubscriptionFragment : PokerAnalyticsFragment(), SkuDetailsResponseListene super.onViewCreated(view, savedInstanceState) initUI() - } - // SkuDetailsResponseListener - override fun onSkuDetailsResponse(responseCode: Int, skuDetailsList: MutableList?) { + private fun initUI() { - if (responseCode == BillingClient.BillingResponse.OK && skuDetailsList != null) { - selectedProduct = skuDetailsList.first { it.sku == IAPProducts.PRO.identifier } - updatePurchaseButtonState() - } + val upgradeString = requireContext().getString(R.string.pro_upgrade) - } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { - private fun initUI() { + val ssb = SpannableStringBuilder(upgradeString) + + val indexOfLastSpace = upgradeString.lastIndexOf(" ") + val end = upgradeString.chars().count().toInt() + + val lightTypeFace = ResourcesCompat.getFont(requireContext(), R.font.roboto_light) + val boldTypeFace = ResourcesCompat.getFont(requireContext(), R.font.roboto_bold) + + if (lightTypeFace != null && boldTypeFace != null) { + ssb.setSpan(TypefaceSpan(lightTypeFace), 0, indexOfLastSpace, Spanned.SPAN_EXCLUSIVE_INCLUSIVE) + ssb.setSpan(TypefaceSpan(boldTypeFace), indexOfLastSpace, end, Spanned.SPAN_EXCLUSIVE_INCLUSIVE) + } + + this.title.text = ssb + } else { + this.title.text = upgradeString + } + + this.purchase.isEnabled = false this.purchase.setOnClickListener { this.selectedProduct?.let { @@ -59,16 +79,37 @@ class SubscriptionFragment : PokerAnalyticsFragment(), SkuDetailsResponseListene throw IllegalStateException("Attempt to initiate purchase while no product has been chosen") } } + } + + // SkuDetailsResponseListener + override fun onSkuDetailsResponse(responseCode: Int, skuDetailsList: MutableList?) { + + if (responseCode == BillingClient.BillingResponse.OK && skuDetailsList != null) { + selectedProduct = skuDetailsList.first { it.sku == IAPProducts.PRO.identifier } + updateUI() + } } - private fun updatePurchaseButtonState() { - this.purchase.isEnabled = (this.selectedProduct != null) + private fun updateUI() { + + this.selectedProduct?.let { + this.purchase.isEnabled = true + val perYearString = requireContext().getString(R.string.year_subscription) + val formattedPrice = it.price + " / " + perYearString + this.price.text = formattedPrice + } + } // PurchaseDelegate - override fun purchaseDidSucceed() { + override fun purchaseDidSucceed(purchase: Purchase) { + + // record purchase in preferences for troubleshooting / verification + val purchaseInfos = listOf(purchase.sku, purchase.orderId, purchase.purchaseToken) + Preferences.setString(Preferences.Keys.LATEST_PURCHASE, purchaseInfos.joinToString("/"), requireContext()) + this.activity?.finish() } diff --git a/app/src/main/java/net/pokeranalytics/android/util/Preferences.kt b/app/src/main/java/net/pokeranalytics/android/util/Preferences.kt index e89ddfa6..0a84d9f2 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/Preferences.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/Preferences.kt @@ -12,7 +12,7 @@ class Preferences { FIRST_LAUNCH("firstLaunch"), STOP_SHOWING_DISCLAIMER("stopShowingDisclaimer"), ACTIVE_FILTER_ID("ActiveFilterId"), - + LATEST_PURCHASE("latestPurchase") } companion object { 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 index 71dbc5b3..c2ce1070 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/billing/AppGuard.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/billing/AppGuard.kt @@ -14,12 +14,14 @@ enum class IAPProducts(var identifier: String) { } interface PurchaseDelegate { - fun purchaseDidSucceed() + fun purchaseDidSucceed(purchase: Purchase) } /** * the AppGuard object is in charge of contacting the Billing services to retrieve products, * initiating purchases and verifying transactions + * Requests performed with the BillingClient must be done while being connected. + * Use executeServiceRequest to ensure this */ object AppGuard : PurchasesUpdatedListener { @@ -56,7 +58,7 @@ object AppGuard : PurchasesUpdatedListener { billingClient = BillingClient.newBuilder(context).setListener(this).build() this.startConnection(Runnable { - this.restorePurchases() + this.updatePurchases() }) } @@ -88,10 +90,17 @@ object AppGuard : PurchasesUpdatedListener { } } + fun requestPurchasesUpdate() { + this.executeServiceRequest(Runnable { + this.updatePurchases() + }) + } + /** - * Restore purchases + * Update the state of subscriptions + * Restore or stop access to IAPs */ - fun restorePurchases() { + private fun updatePurchases() { this.resetPurchases() // Automatically checks for purchases (when switching devices for example) val purchasesResult = @@ -172,8 +181,6 @@ object AppGuard : PurchasesUpdatedListener { */ private fun handlePurchase(purchase: Purchase) { -// val token = purchase.purchaseToken - if (this.verifyValidSignature(purchase.originalJson, purchase.signature)) { when (purchase.sku) { IAPProducts.PRO.identifier -> { @@ -185,7 +192,7 @@ object AppGuard : PurchasesUpdatedListener { this.isProUser = true this.purchaseDelegate?.let { - it.purchaseDidSucceed() + it.purchaseDidSucceed(purchase) this.purchaseDelegate = null } } diff --git a/app/src/main/res/layout/fragment_subscription.xml b/app/src/main/res/layout/fragment_subscription.xml index 6a12586b..8d876b7d 100644 --- a/app/src/main/res/layout/fragment_subscription.xml +++ b/app/src/main/res/layout/fragment_subscription.xml @@ -3,8 +3,34 @@ xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" + xmlns:tools="http://schemas.android.com/tools" android:orientation="vertical"> + + + + + + - + android:layout_marginLeft="16dp" + android:layout_marginRight="16dp" + android:layout_marginBottom="42dp" + android:text="@string/pro_purchase" /> \ 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 26f159bb..30fd8f01 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -11,7 +11,9 @@ 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. + The billing services are unavailable at the moment. Please check your internet connection and retry later. + Upgrade to Pro + Go Pro Address Naming suggestions diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 49ebcaa0..4d179335 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -303,4 +303,24 @@ + + + + + + + +