Subscriptions update

dev
Laurent 7 years ago
parent d62d6332f7
commit 3958396ce6
  1. 1
      app/src/main/java/net/pokeranalytics/android/ui/activity/BillingActivity.kt
  2. 9
      app/src/main/java/net/pokeranalytics/android/ui/activity/HomeActivity.kt
  3. 3
      app/src/main/java/net/pokeranalytics/android/ui/extensions/UIExtensions.kt
  4. 2
      app/src/main/java/net/pokeranalytics/android/ui/fragment/FeedFragment.kt
  5. 67
      app/src/main/java/net/pokeranalytics/android/ui/fragment/SubscriptionFragment.kt
  6. 2
      app/src/main/java/net/pokeranalytics/android/util/Preferences.kt
  7. 21
      app/src/main/java/net/pokeranalytics/android/util/billing/AppGuard.kt
  8. 33
      app/src/main/res/layout/fragment_subscription.xml
  9. 4
      app/src/main/res/values/strings.xml
  10. 20
      app/src/main/res/values/styles.xml

@ -24,7 +24,6 @@ class BillingActivity : PokerAnalyticsActivity() {
override fun onResume() {
super.onResume()
AppGuard.restorePurchases()
}
}

@ -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)

@ -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)

@ -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
// }

@ -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<SkuDetails>?) {
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) {
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)
}
private fun initUI() {
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<SkuDetails>?) {
if (responseCode == BillingClient.BillingResponse.OK && skuDetailsList != null) {
selectedProduct = skuDetailsList.first { it.sku == IAPProducts.PRO.identifier }
updateUI()
}
}
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
}
private fun updatePurchaseButtonState() {
this.purchase.isEnabled = (this.selectedProduct != null)
}
// 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()
}

@ -12,7 +12,7 @@ class Preferences {
FIRST_LAUNCH("firstLaunch"),
STOP_SHOWING_DISCLAIMER("stopShowingDisclaimer"),
ACTIVE_FILTER_ID("ActiveFilterId"),
LATEST_PURCHASE("latestPurchase")
}
companion object {

@ -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
}
}

@ -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">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/title"
android:layout_marginTop="24dp"
android:text="@string/pro_upgrade"
style="@style/PokerAnalyticsTheme.TextView.SubscriptionTitle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:gravity="center"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<!-- -->
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/price"
style="@style/PokerAnalyticsTheme.TextView.SubscriptionPrice"
app:layout_constraintBottom_toTopOf="@id/purchase"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:gravity="center"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:text="$29.99 / year"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/purchase"
style="@style/PokerAnalyticsTheme.Button"
@ -13,8 +39,9 @@
app:layout_constraintBottom_toBottomOf="parent"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:layout_margin="16dp"
android:text="@string/purchase" />
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginBottom="42dp"
android:text="@string/pro_purchase" />
</androidx.constraintlayout.widget.ConstraintLayout>

@ -11,7 +11,9 @@
<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="billingclient_unavailable">The billing services are unavailable at the moment. Please check your internet connection and retry later.</string>
<string name="pro_upgrade">Upgrade to Pro</string>
<string name="pro_purchase">Go Pro</string>
<string name="address">Address</string>
<string name="suggestions">Naming suggestions</string>

@ -303,4 +303,24 @@
</style>
<!-- Subscription -->
<style name="PokerAnalyticsTheme.TextView.SubscriptionTitle">
<item name="android:textColor">@color/white</item>
<item name="android:maxLines">1</item>
<item name="android:ellipsize">end</item>
<item name="android:fontFamily">@font/roboto_light</item>
<item name="android:textSize">36sp</item>
</style>
<style name="PokerAnalyticsTheme.TextView.SubscriptionPrice">
<item name="android:textColor">@color/white</item>
<item name="android:maxLines">1</item>
<item name="android:ellipsize">end</item>
<item name="android:fontFamily">@font/roboto_light</item>
<item name="android:textSize">28sp</item>
</style>
</resources>

Loading…
Cancel
Save