Upgrade billing API to 3.0

filterfix
Laurent 5 years ago
parent 6c36863185
commit ec5e6b427e
  1. 8
      app/build.gradle
  2. 1
      app/src/main/AndroidManifest.xml
  3. 9
      app/src/main/java/net/pokeranalytics/android/ui/fragment/SettingsFragment.kt
  4. 16
      app/src/main/java/net/pokeranalytics/android/ui/fragment/StatisticsFragment.kt
  5. 15
      app/src/main/java/net/pokeranalytics/android/ui/fragment/SubscriptionFragment.kt
  6. 38
      app/src/main/java/net/pokeranalytics/android/ui/fragment/components/RealmFragment.kt
  7. 4
      app/src/main/java/net/pokeranalytics/android/ui/modules/calendar/CalendarDetailsFragment.kt
  8. 15
      app/src/main/java/net/pokeranalytics/android/ui/modules/calendar/CalendarFragment.kt
  9. 4
      app/src/main/java/net/pokeranalytics/android/ui/modules/data/DataManagerFragment.kt
  10. 22
      app/src/main/java/net/pokeranalytics/android/ui/modules/datalist/DataListActivity.kt
  11. 8
      app/src/main/java/net/pokeranalytics/android/ui/modules/datalist/DataListFragment.kt
  12. 4
      app/src/main/java/net/pokeranalytics/android/ui/modules/filter/FilterDetailsFragment.kt
  13. 4
      app/src/main/java/net/pokeranalytics/android/ui/modules/filter/FiltersActivity.kt
  14. 6
      app/src/main/java/net/pokeranalytics/android/ui/modules/filter/FiltersFragment.kt
  15. 4
      app/src/main/java/net/pokeranalytics/android/ui/modules/filter/FiltersListActivity.kt
  16. 7
      app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/editor/EditorFragment.kt
  17. 4
      app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/replayer/ReplayerFragment.kt
  18. 4
      app/src/main/java/net/pokeranalytics/android/ui/modules/session/SessionActivity.kt
  19. 7
      app/src/main/java/net/pokeranalytics/android/ui/modules/session/SessionFragment.kt
  20. 71
      app/src/main/java/net/pokeranalytics/android/util/billing/AppGuard.kt
  21. 25
      app/src/main/java/net/pokeranalytics/android/util/billing/Security.kt
  22. 4
      app/src/main/java/net/pokeranalytics/android/util/csv/CSVImporter.kt
  23. 1
      app/src/main/res/values/strings.xml

@ -34,8 +34,8 @@ android {
applicationId "net.pokeranalytics.android" applicationId "net.pokeranalytics.android"
minSdkVersion 23 minSdkVersion 23
targetSdkVersion 29 targetSdkVersion 29
versionCode 105 versionCode 108
versionName "5.0.7" versionName "5.0.8"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
} }
@ -112,9 +112,7 @@ dependencies {
implementation 'com.google.android.libraries.places:places:2.3.0' implementation 'com.google.android.libraries.places:places:2.3.0'
// Billing / Subscriptions // Billing / Subscriptions
// WARNING FOR 2.0: https://developer.android.com/google/play/billing/billing_library_releases_notes implementation 'com.android.billingclient:billing:3.0.0'
// Purchases MUST BE ACKNOWLEDGED
implementation 'com.android.billingclient:billing:1.2.2'
// Firebase // Firebase
implementation 'com.google.firebase:firebase-core:17.5.0' implementation 'com.google.firebase:firebase-core:17.5.0'

@ -5,7 +5,6 @@
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="com.android.vending.BILLING" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

@ -47,7 +47,6 @@ import java.util.*
class SettingsFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRepresentableDataSource { class SettingsFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRepresentableDataSource {
companion object { companion object {
/** /**
@ -143,7 +142,7 @@ class SettingsFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRep
if (!AppGuard.isProUser) { if (!AppGuard.isProUser) {
BillingActivity.newInstanceForResult(this, false) BillingActivity.newInstanceForResult(this, false)
} else { } else {
this.openPlaystoreAccount() this.openPlayStoreAccount()
} }
} }
SettingRow.RATE_APP -> showReviewManager() SettingRow.RATE_APP -> showReviewManager()
@ -175,8 +174,8 @@ class SettingsFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRep
private fun showReviewManager() { private fun showReviewManager() {
val manager = ReviewManagerFactory.create(requireContext()) val manager = ReviewManagerFactory.create(requireContext())
val request = manager.requestReviewFlow() val task = manager.requestReviewFlow()
request.addOnCompleteListener { request -> task.addOnCompleteListener { request ->
if (request.isSuccessful) { if (request.isSuccessful) {
// We got the ReviewInfo object // We got the ReviewInfo object
val reviewInfo = request.result val reviewInfo = request.result
@ -243,7 +242,7 @@ class SettingsFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRep
/** /**
* Open Google Play account * Open Google Play account
*/ */
private fun openPlaystoreAccount() { private fun openPlayStoreAccount() {
val packageName = "net.pokeranalytics.android" val packageName = "net.pokeranalytics.android"
val sku = IAPProducts.PRO.identifier val sku = IAPProducts.PRO.identifier

@ -7,7 +7,6 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import io.realm.Realm import io.realm.Realm
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -59,7 +58,7 @@ class StatisticsFragment : FilterableFragment(), RealmAsyncListener {
initUI() initUI()
this.currentFilterable = FilterableType.SESSION this.currentFilterable = FilterableType.SESSION
applyFilter() applyFilter()
listenAsynchronously(this, ComputableResult::class.java) listenRealmChanges(this, ComputableResult::class.java)
} }
private fun initUI() { private fun initUI() {
@ -117,13 +116,14 @@ class StatisticsFragment : FilterableFragment(), RealmAsyncListener {
// Business // Business
override fun asyncListenedEntityChange(realm: Realm) { override fun asyncListenedEntityChange(realm: Realm) {
launchStatComputation()
val report = createSessionGroupsAndStartCompute(realm) // val report = createSessionGroupsAndStartCompute(realm)
tableReportFragment.report = report // tableReportFragment.report = report
//
GlobalScope.launch(Dispatchers.Main) { // GlobalScope.launch(Dispatchers.Main) {
tableReportFragment.showResults() // tableReportFragment.showResults()
} // }
} }

@ -21,10 +21,7 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentStatePagerAdapter import androidx.fragment.app.FragmentStatePagerAdapter
import androidx.viewpager.widget.ViewPager import androidx.viewpager.widget.ViewPager
import com.android.billingclient.api.BillingClient import com.android.billingclient.api.*
import com.android.billingclient.api.Purchase
import com.android.billingclient.api.SkuDetails
import com.android.billingclient.api.SkuDetailsResponseListener
import com.crashlytics.android.Crashlytics import com.crashlytics.android.Crashlytics
import kotlinx.android.synthetic.main.fragment_subscription.* import kotlinx.android.synthetic.main.fragment_subscription.*
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
@ -128,7 +125,7 @@ class SubscriptionFragment : BaseFragment(), SkuDetailsResponseListener, Purchas
// Pager // Pager
// The pager adapter, which provides the pages to the view pager widget. // The pager adapter, which provides the pages to the view pager widget.
this.pagerAdapter = ScreenSlidePagerAdapter(requireFragmentManager()) this.pagerAdapter = ScreenSlidePagerAdapter(parentFragmentManager)
this.pager.adapter = pagerAdapter this.pager.adapter = pagerAdapter
this.pager.addOnPageChangeListener(this) this.pager.addOnPageChangeListener(this)
@ -196,10 +193,10 @@ class SubscriptionFragment : BaseFragment(), SkuDetailsResponseListener, Purchas
} }
// SkuDetailsResponseListener // SkuDetailsResponseListener
override fun onSkuDetailsResponse(responseCode: Int, skuDetailsList: MutableList<SkuDetails>?) { override fun onSkuDetailsResponse(result: BillingResult, skuDetailsList: MutableList<SkuDetails>?) {
if (responseCode == BillingClient.BillingResponse.OK && skuDetailsList != null) { if (result.responseCode == BillingClient.BillingResponseCode.OK) {
this.hideLoader() this.hideLoader()
selectedProduct = skuDetailsList.first { it.sku == IAPProducts.PRO.identifier } selectedProduct = skuDetailsList?.firstOrNull { it.sku == IAPProducts.PRO.identifier }
updateUI() updateUI()
} }
} }
@ -223,6 +220,8 @@ class SubscriptionFragment : BaseFragment(), SkuDetailsResponseListener, Purchas
val formattedFreeTrial = val formattedFreeTrial =
"$freeTrialDays " + requireContext().getString(R.string.days) + " " + requireContext().getString(R.string.free_trial) "$freeTrialDays " + requireContext().getString(R.string.days) + " " + requireContext().getString(R.string.free_trial)
this.freetrial.text = formattedFreeTrial this.freetrial.text = formattedFreeTrial
} ?: run {
Toast.makeText(requireContext(), R.string.contact_support, Toast.LENGTH_LONG).show()
} }
} }

@ -1,7 +1,6 @@
package net.pokeranalytics.android.ui.fragment.components package net.pokeranalytics.android.ui.fragment.components
import android.os.Bundle import android.os.Bundle
import android.os.Looper
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -9,9 +8,6 @@ import io.realm.Realm
import io.realm.RealmModel import io.realm.RealmModel
import io.realm.RealmResults import io.realm.RealmResults
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
@ -29,17 +25,12 @@ open class RealmFragment : BaseFragment() {
*/ */
private lateinit var realm: Realm private lateinit var realm: Realm
/***
* A background realm instance
*/
private var asyncRealm: Realm? = null
/*** /***
* A listener to async updates * A listener to async updates
*/ */
private var asyncListener: RealmAsyncListener? = null private var changeListener: RealmAsyncListener? = null
private var asyncResults: RealmResults<out RealmModel>? = null private var realmResults: RealmResults<out RealmModel>? = null
/** /**
* A List of observed RealmResults * A List of observed RealmResults
@ -62,24 +53,14 @@ open class RealmFragment : BaseFragment() {
return super.onCreateView(inflater, container, savedInstanceState) return super.onCreateView(inflater, container, savedInstanceState)
} }
fun listenAsynchronously(listener: RealmAsyncListener, clazz: Class<out RealmModel>) { fun listenRealmChanges(listener: RealmAsyncListener, clazz: Class<out RealmModel>) {
this.asyncListener = listener this.changeListener = listener
GlobalScope.launch(coroutineContext) { this.realmResults = this.realm.where(clazz).findAllAsync()
val async = GlobalScope.async { this.realmResults?.addChangeListener { t, _ ->
Looper.prepare() // a looper is required on the thread to listen to change Timber.d("Realm changes: ${realmResults?.size}, $this")
Looper.loop() this.changeListener?.asyncListenedEntityChange(t.realm)
val realm = Realm.getDefaultInstance()
asyncResults = realm.where(clazz).findAll()
asyncResults?.addChangeListener { t, _ ->
Timber.d("Realm changes: ${asyncResults?.size}, ${this@RealmFragment}")
asyncListener?.asyncListenedEntityChange(t.realm)
}
asyncRealm = realm
}
async.await()
} }
} }
@ -92,8 +73,7 @@ open class RealmFragment : BaseFragment() {
} }
this.realm.close() this.realm.close()
this.asyncResults?.removeAllChangeListeners() this.realmResults?.removeAllChangeListeners()
this.asyncRealm?.close()
} }
/** /**

@ -7,7 +7,7 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.lifecycle.ViewModelProviders import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.github.mikephil.charting.data.BarDataSet import com.github.mikephil.charting.data.BarDataSet
import com.github.mikephil.charting.data.LineDataSet import com.github.mikephil.charting.data.LineDataSet
@ -40,7 +40,7 @@ import kotlin.collections.ArrayList
class CalendarDetailsFragment : BaseFragment(), StaticRowRepresentableDataSource, RowRepresentableDelegate { class CalendarDetailsFragment : BaseFragment(), StaticRowRepresentableDataSource, RowRepresentableDelegate {
private val model: CalendarDetailsViewModel by lazy { private val model: CalendarDetailsViewModel by lazy {
ViewModelProviders.of(requireActivity()).get(CalendarDetailsViewModel::class.java) ViewModelProvider(requireActivity()).get(CalendarDetailsViewModel::class.java)
} }
companion object { companion object {

@ -83,7 +83,7 @@ class CalendarFragment : RealmFragment(), CoroutineScope, StaticRowRepresentable
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
initData() initData()
initUI() initUI()
listenAsynchronously(this, ComputableResult::class.java) listenRealmChanges(this, ComputableResult::class.java)
} }
override fun adapterRows(): List<RowRepresentable>? { override fun adapterRows(): List<RowRepresentable>? {
@ -417,12 +417,15 @@ class CalendarFragment : RealmFragment(), CoroutineScope, StaticRowRepresentable
} }
override fun asyncListenedEntityChange(realm: Realm) { override fun asyncListenedEntityChange(realm: Realm) {
Timber.d("asyncListenedEntityChange")
launchStatComputation(realm)
activity?.runOnUiThread { launchAsyncStatComputation()
displayData()
} // Timber.d("asyncListenedEntityChange")
// launchStatComputation(realm)
//
// activity?.runOnUiThread {
// displayData()
// }
} }
} }

@ -8,7 +8,7 @@ import android.view.MenuInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.ViewModelProviders import androidx.lifecycle.ViewModelProvider
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.exceptions.ConfigurationException import net.pokeranalytics.android.exceptions.ConfigurationException
import net.pokeranalytics.android.model.interfaces.Savable import net.pokeranalytics.android.model.interfaces.Savable
@ -20,7 +20,7 @@ import net.pokeranalytics.android.ui.viewmodel.DataManagerViewModel
open class DataManagerFragment : RealmFragment() { open class DataManagerFragment : RealmFragment() {
protected val model: DataManagerViewModel by lazy { protected val model: DataManagerViewModel by lazy {
ViewModelProviders.of(this).get(DataManagerViewModel::class.java) ViewModelProvider(this).get(DataManagerViewModel::class.java)
} }
// lateinit var item: Deletable // lateinit var item: Deletable

@ -4,7 +4,7 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProviders import androidx.lifecycle.ViewModelProvider
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.LiveData import net.pokeranalytics.android.model.LiveData
import net.pokeranalytics.android.ui.activity.components.BaseActivity import net.pokeranalytics.android.ui.activity.components.BaseActivity
@ -14,7 +14,7 @@ import net.pokeranalytics.android.ui.modules.filter.FilterActivityRequestCode
class DataListActivity : BaseActivity() { class DataListActivity : BaseActivity() {
val model: DataListViewModel by lazy { val model: DataListViewModel by lazy {
ViewModelProviders.of(this).get(DataListViewModel::class.java) ViewModelProvider(this).get(DataListViewModel::class.java)
} }
enum class IntentKey(val keyName: String) { enum class IntentKey(val keyName: String) {
@ -37,15 +37,15 @@ class DataListActivity : BaseActivity() {
) )
} }
fun newSelectInstance(fragment: Fragment, dataType: Int, showAddButton: Boolean = true) { // fun newSelectInstance(fragment: Fragment, dataType: Int, showAddButton: Boolean = true) {
val context = fragment.requireContext() // val context = fragment.requireContext()
fragment.startActivityForResult( // fragment.startActivityForResult(
getIntent( // getIntent(
context, // context,
dataType, // dataType,
showAddButton // showAddButton
), FilterActivityRequestCode.SELECT_FILTER.ordinal) // ), FilterActivityRequestCode.SELECT_FILTER.ordinal)
} // }
fun newInstance(fragment: Fragment, dataType: LiveData, selection: Boolean, itemIds: Array<String>? = null, showAddButton: Boolean = true) { fun newInstance(fragment: Fragment, dataType: LiveData, selection: Boolean, itemIds: Array<String>? = null, showAddButton: Boolean = true) {
val context = fragment.requireContext() val context = fragment.requireContext()

@ -6,7 +6,7 @@ import android.os.Bundle
import android.view.* import android.view.*
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.lifecycle.ViewModelProviders import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import io.realm.Realm import io.realm.Realm
@ -18,14 +18,14 @@ import net.pokeranalytics.android.model.LiveData
import net.pokeranalytics.android.model.interfaces.Deletable import net.pokeranalytics.android.model.interfaces.Deletable
import net.pokeranalytics.android.model.interfaces.Identifiable import net.pokeranalytics.android.model.interfaces.Identifiable
import net.pokeranalytics.android.model.realm.Filter import net.pokeranalytics.android.model.realm.Filter
import net.pokeranalytics.android.ui.modules.data.EditableDataActivity
import net.pokeranalytics.android.ui.modules.filter.FiltersActivity
import net.pokeranalytics.android.ui.activity.components.RequestCode import net.pokeranalytics.android.ui.activity.components.RequestCode
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
import net.pokeranalytics.android.ui.extensions.removeMargins import net.pokeranalytics.android.ui.extensions.removeMargins
import net.pokeranalytics.android.ui.fragment.components.DeletableItemFragment import net.pokeranalytics.android.ui.fragment.components.DeletableItemFragment
import net.pokeranalytics.android.ui.helpers.SwipeToDeleteCallback import net.pokeranalytics.android.ui.helpers.SwipeToDeleteCallback
import net.pokeranalytics.android.ui.modules.data.EditableDataActivity
import net.pokeranalytics.android.ui.modules.filter.FiltersActivity
import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.util.extensions.find import net.pokeranalytics.android.util.extensions.find
import net.pokeranalytics.android.util.extensions.sorted import net.pokeranalytics.android.util.extensions.sorted
@ -34,7 +34,7 @@ import net.pokeranalytics.android.util.extensions.sorted
open class DataListFragment : DeletableItemFragment(), RowRepresentableDelegate { open class DataListFragment : DeletableItemFragment(), RowRepresentableDelegate {
val model: DataListViewModel by lazy { val model: DataListViewModel by lazy {
ViewModelProviders.of(requireActivity()).get(DataListViewModel::class.java) ViewModelProvider(requireActivity()).get(DataListViewModel::class.java)
} }
companion object { companion object {

@ -5,7 +5,7 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.lifecycle.ViewModelProviders import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import kotlinx.android.synthetic.main.fragment_filter_details.* import kotlinx.android.synthetic.main.fragment_filter_details.*
import kotlinx.android.synthetic.main.fragment_filter_details.view.* import kotlinx.android.synthetic.main.fragment_filter_details.view.*
@ -28,7 +28,7 @@ import kotlin.collections.ArrayList
open class FilterDetailsFragment : RealmFragment(), StaticRowRepresentableDataSource, RowRepresentableDelegate { open class FilterDetailsFragment : RealmFragment(), StaticRowRepresentableDataSource, RowRepresentableDelegate {
val model: FilterViewModel by lazy { val model: FilterViewModel by lazy {
ViewModelProviders.of(requireActivity()).get(FilterViewModel::class.java) ViewModelProvider(requireActivity()).get(FilterViewModel::class.java)
} }
private lateinit var rowRepresentableAdapter: RowRepresentableAdapter private lateinit var rowRepresentableAdapter: RowRepresentableAdapter

@ -4,7 +4,7 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProviders import androidx.lifecycle.ViewModelProvider
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.ui.activity.components.BaseActivity import net.pokeranalytics.android.ui.activity.components.BaseActivity
import net.pokeranalytics.android.ui.fragment.components.BaseFragment import net.pokeranalytics.android.ui.fragment.components.BaseFragment
@ -12,7 +12,7 @@ import net.pokeranalytics.android.ui.fragment.components.BaseFragment
class FiltersActivity : BaseActivity() { class FiltersActivity : BaseActivity() {
val model: FilterViewModel by lazy { val model: FilterViewModel by lazy {
ViewModelProviders.of(this).get(FilterViewModel::class.java) ViewModelProvider(this).get(FilterViewModel::class.java)
} }
enum class IntentKey(val keyName: String) { enum class IntentKey(val keyName: String) {

@ -5,7 +5,7 @@ import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.* import android.view.*
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.lifecycle.ViewModelProviders import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.chip.Chip import com.google.android.material.chip.Chip
import kotlinx.android.synthetic.main.fragment_editable_data.appBar import kotlinx.android.synthetic.main.fragment_editable_data.appBar
@ -29,7 +29,7 @@ import timber.log.Timber
open class FiltersFragment : RealmFragment(), RowRepresentableDelegate { open class FiltersFragment : RealmFragment(), RowRepresentableDelegate {
val model: FilterViewModel by lazy { val model: FilterViewModel by lazy {
ViewModelProviders.of(requireActivity()).get(FilterViewModel::class.java) ViewModelProvider(requireActivity()).get(FilterViewModel::class.java)
} }
companion object { companion object {
@ -94,8 +94,6 @@ open class FiltersFragment : RealmFragment(), RowRepresentableDelegate {
override fun onBackPressed() { override fun onBackPressed() {
if (this.model.isUpdating) { if (this.model.isUpdating) {
cancelUpdates() cancelUpdates()
} else {
// activity?.finish()
} }
} }

@ -4,7 +4,7 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProviders import androidx.lifecycle.ViewModelProvider
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.LiveData import net.pokeranalytics.android.model.LiveData
import net.pokeranalytics.android.ui.activity.components.BaseActivity import net.pokeranalytics.android.ui.activity.components.BaseActivity
@ -13,7 +13,7 @@ import net.pokeranalytics.android.ui.modules.datalist.DataListViewModel
class FiltersListActivity : BaseActivity() { class FiltersListActivity : BaseActivity() {
val model: DataListViewModel by lazy { val model: DataListViewModel by lazy {
ViewModelProviders.of(this).get(DataListViewModel::class.java) ViewModelProvider(this).get(DataListViewModel::class.java)
} }
enum class IntentKey(val keyName: String) { enum class IntentKey(val keyName: String) {

@ -10,7 +10,7 @@ import android.view.*
import android.view.animation.AccelerateDecelerateInterpolator import android.view.animation.AccelerateDecelerateInterpolator
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders import androidx.lifecycle.ViewModelProvider
import kotlinx.android.synthetic.main.fragment_hand_history.* import kotlinx.android.synthetic.main.fragment_hand_history.*
import kotlinx.android.synthetic.main.fragment_settings.recyclerView import kotlinx.android.synthetic.main.fragment_settings.recyclerView
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
@ -27,7 +27,6 @@ import net.pokeranalytics.android.ui.extensions.px
import net.pokeranalytics.android.ui.extensions.showAlertDialog import net.pokeranalytics.android.ui.extensions.showAlertDialog
import net.pokeranalytics.android.ui.fragment.components.BaseFragment import net.pokeranalytics.android.ui.fragment.components.BaseFragment
import net.pokeranalytics.android.ui.fragment.components.RealmFragment import net.pokeranalytics.android.ui.fragment.components.RealmFragment
import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetFragment
import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetType import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetType
import net.pokeranalytics.android.ui.modules.datalist.DataListActivity import net.pokeranalytics.android.ui.modules.datalist.DataListActivity
import net.pokeranalytics.android.ui.modules.handhistory.HandHistoryActivity import net.pokeranalytics.android.ui.modules.handhistory.HandHistoryActivity
@ -87,7 +86,7 @@ class EditorFragment : RealmFragment(), RowRepresentableDelegate, KeyboardListen
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
this.model = activity?.run { this.model = activity?.run {
ViewModelProviders.of(this)[EditorViewModel::class.java] ViewModelProvider(this)[EditorViewModel::class.java]
} ?: throw Exception("Invalid Activity") } ?: throw Exception("Invalid Activity")
} }
@ -153,7 +152,7 @@ class EditorFragment : RealmFragment(), RowRepresentableDelegate, KeyboardListen
val observer = Observer<MutableList<RowRepresentable>> { val observer = Observer<MutableList<RowRepresentable>> {
this.editorAdapter.notifyDataSetChanged() this.editorAdapter.notifyDataSetChanged()
} }
this.model.rowsLiveData.observe(this, observer) this.model.rowsLiveData.observe(viewLifecycleOwner, observer)
// At first, the selection is defined before the holder is bound, // At first, the selection is defined before the holder is bound,
// so we retrieve the editText inputConnection once the recycler view has been rendered // so we retrieve the editText inputConnection once the recycler view has been rendered

@ -6,7 +6,7 @@ import android.os.Looper
import android.view.* import android.view.*
import android.widget.PopupWindow import android.widget.PopupWindow
import android.widget.Switch import android.widget.Switch
import androidx.lifecycle.ViewModelProviders import androidx.lifecycle.ViewModelProvider
import io.realm.Sort import io.realm.Sort
import kotlinx.android.synthetic.main.fragment_replayer.* import kotlinx.android.synthetic.main.fragment_replayer.*
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
@ -42,7 +42,7 @@ class ReplayerFragment : RealmFragment() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
this.model = activity?.run { this.model = activity?.run {
ViewModelProviders.of(this)[ReplayerModel::class.java] ViewModelProvider(this)[ReplayerModel::class.java]
} ?: throw Exception("Invalid Activity") } ?: throw Exception("Invalid Activity")
} }

@ -5,7 +5,7 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProviders import androidx.lifecycle.ViewModelProvider
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.ui.activity.components.BaseActivity import net.pokeranalytics.android.ui.activity.components.BaseActivity
@ -14,7 +14,7 @@ class SessionActivity: BaseActivity() {
private lateinit var sessionFragment: SessionFragment private lateinit var sessionFragment: SessionFragment
val model: SessionViewModel by lazy { val model: SessionViewModel by lazy {
ViewModelProviders.of(this).get(SessionViewModel::class.java) ViewModelProvider(this).get(SessionViewModel::class.java)
} }
enum class IntentKey(val keyName : String) { enum class IntentKey(val keyName : String) {

@ -7,11 +7,10 @@ import android.view.animation.OvershootInterpolator
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.interpolator.view.animation.FastOutSlowInInterpolator import androidx.interpolator.view.animation.FastOutSlowInInterpolator
import androidx.lifecycle.ViewModelProviders import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import com.crashlytics.android.Crashlytics import com.crashlytics.android.Crashlytics
import kotlinx.android.synthetic.main.fragment_session.* import kotlinx.android.synthetic.main.fragment_session.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -45,8 +44,6 @@ import net.pokeranalytics.android.util.extensions.formattedHourlyDuration
import net.pokeranalytics.android.util.extensions.getNextMinuteInMilliseconds import net.pokeranalytics.android.util.extensions.getNextMinuteInMilliseconds
import timber.log.Timber import timber.log.Timber
import java.util.* import java.util.*
import kotlin.coroutines.CoroutineContext
class SessionFragment : RealmFragment(), RowRepresentableDelegate { class SessionFragment : RealmFragment(), RowRepresentableDelegate {
@ -93,7 +90,7 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
this.viewModel = activity?.run { this.viewModel = activity?.run {
ViewModelProviders.of(this).get(SessionViewModel::class.java) ViewModelProvider(this).get(SessionViewModel::class.java)
} ?: throw Exception("Invalid Activity") } ?: throw Exception("Invalid Activity")
} }

@ -5,6 +5,7 @@ import android.content.Context
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import com.android.billingclient.api.* import com.android.billingclient.api.*
import com.android.billingclient.api.BillingClient.BillingResponseCode
import net.pokeranalytics.android.BuildConfig import net.pokeranalytics.android.BuildConfig
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import timber.log.Timber import timber.log.Timber
@ -99,7 +100,7 @@ object AppGuard : PurchasesUpdatedListener {
*/ */
fun load(context: Context) { fun load(context: Context) {
billingClient = BillingClient.newBuilder(context).setListener(this).build() billingClient = BillingClient.newBuilder(context).setListener(this).enablePendingPurchases().build()
this.startConnection(Runnable { this.startConnection(Runnable {
this.updatePurchases() this.updatePurchases()
@ -110,10 +111,9 @@ object AppGuard : PurchasesUpdatedListener {
private fun startConnection(executeOnSuccess: Runnable) { private fun startConnection(executeOnSuccess: Runnable) {
billingClient.startConnection(object : BillingClientStateListener { billingClient.startConnection(object : BillingClientStateListener {
override fun onBillingSetupFinished(@BillingClient.BillingResponse billingResponseCode: Int) { override fun onBillingSetupFinished(result: BillingResult) {
this@AppGuard.billingResponseCode = result.responseCode
this@AppGuard.billingResponseCode = billingResponseCode if (billingResponseCode == BillingResponseCode.OK) {
if (billingResponseCode == BillingClient.BillingResponse.OK) {
// The BillingClient is ready. You can query purchases here. // The BillingClient is ready. You can query purchases here.
billingClientAvailable = true billingClientAvailable = true
executeOnSuccess.run() executeOnSuccess.run()
@ -125,6 +125,22 @@ object AppGuard : PurchasesUpdatedListener {
} }
}) })
// billingClient.startConnection(object : BillingClientStateListener {
// override fun onBillingSetupFinished(@BillingClient.BillingResponse billingResponseCode: Int) {
//
// this@AppGuard.billingResponseCode = billingResponseCode
// if (billingResponseCode == BillingClient.BillingResponse.OK) {
// // The BillingClient is ready. You can query purchases here.
// billingClientAvailable = true
// executeOnSuccess.run()
// }
// }
//
// override fun onBillingServiceDisconnected() {
// billingClientAvailable = false
// }
// })
} }
private fun executeServiceRequest(runnable: Runnable) { private fun executeServiceRequest(runnable: Runnable) {
@ -150,8 +166,8 @@ object AppGuard : PurchasesUpdatedListener {
this.resetPurchases() this.resetPurchases()
// Automatically checks for purchases (when switching devices for example) // Automatically checks for purchases (when switching devices for example)
val purchasesResult = billingClient.queryPurchases(BillingClient.SkuType.SUBS) val purchasesResult = billingClient.queryPurchases(BillingClient.SkuType.SUBS)
if (purchasesResult != null && purchasesResult.purchasesList != null) { if (purchasesResult != null) {
purchasesResult.purchasesList.forEach { purchasesResult.purchasesList?.forEach {
this.handlePurchase(it) this.handlePurchase(it)
} }
} }
@ -206,23 +222,40 @@ object AppGuard : PurchasesUpdatedListener {
// PurchasesUpdatedListener // PurchasesUpdatedListener
/** override fun onPurchasesUpdated(result: BillingResult, purchases: MutableList<Purchase>?) {
* Purchase callback
*/
override fun onPurchasesUpdated(responseCode: Int, purchases: MutableList<Purchase>?) {
if (responseCode == BillingClient.BillingResponse.OK && purchases != null) { if (result.responseCode == BillingResponseCode.OK && purchases != null) {
for (purchase in purchases) { for (purchase in purchases) {
handlePurchase(purchase) handlePurchase(purchase)
} }
} else if (responseCode == BillingClient.BillingResponse.USER_CANCELED) { } else if (result.responseCode == BillingResponseCode.USER_CANCELED) {
Timber.d("purchase cancel message: ${result.debugMessage}")
// Handle an error caused by a user cancelling the purchase flow. // Handle an error caused by a user cancelling the purchase flow.
} else { } else {
Timber.d("purchase error message: ${result.debugMessage}")
// Handle any other error codes. // Handle any other error codes.
} }
} }
// /**
// * Purchase callback
// */
// 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.
// }
//
// }
/** /**
* Method called when a [purchase] has been made * Method called when a [purchase] has been made
*/ */
@ -232,11 +265,19 @@ object AppGuard : PurchasesUpdatedListener {
when (purchase.sku) { when (purchase.sku) {
IAPProducts.PRO.identifier -> { IAPProducts.PRO.identifier -> {
val date = Date(purchase.purchaseTime) val date = Date(purchase.purchaseTime)
Timber.d("*** Auto renewing = ${purchase.isAutoRenewing}") Timber.d("*** Auto renewing = ${purchase.isAutoRenewing}")
Timber.d("*** purchaseTime = $date") Timber.d("*** purchaseTime = $date")
if (purchase.purchaseState == Purchase.PurchaseState.PURCHASED) {
if (!purchase.isAcknowledged) {
val params = AcknowledgePurchaseParams.newBuilder().setPurchaseToken(purchase.purchaseToken).build()
this.billingClient.acknowledgePurchase(params) { result ->
Timber.d("Acknowledge result: ${result.responseCode}")
}
}
this._isProUser = true this._isProUser = true
this.purchaseDelegate?.let { this.purchaseDelegate?.let {
Handler(Looper.getMainLooper()).post { Handler(Looper.getMainLooper()).post {
@ -245,6 +286,8 @@ object AppGuard : PurchasesUpdatedListener {
this.purchaseDelegate = null this.purchaseDelegate = null
} }
} }
}
else -> { else -> {
} }
} }

@ -17,10 +17,9 @@ package net.pokeranalytics.android.util.billing
*/ */
import android.text.TextUtils import android.text.TextUtils
import android.util.Base64 import android.util.Base64
import com.android.billingclient.util.BillingHelper import timber.log.Timber
import java.io.IOException import java.io.IOException
import java.security.* import java.security.*
import java.security.spec.InvalidKeySpecException import java.security.spec.InvalidKeySpecException
@ -32,10 +31,8 @@ import java.security.spec.X509EncodedKeySpec
*/ */
object Security { object Security {
private val TAG = "IABUtil/Security" private const val KEY_FACTORY_ALGORITHM = "RSA"
private const val SIGNATURE_ALGORITHM = "SHA1withRSA"
private val KEY_FACTORY_ALGORITHM = "RSA"
private val SIGNATURE_ALGORITHM = "SHA1withRSA"
/** /**
* Verifies that the data was signed with the given signature, and returns the verified * Verifies that the data was signed with the given signature, and returns the verified
@ -54,7 +51,7 @@ object Security {
if (TextUtils.isEmpty(signedData) || TextUtils.isEmpty(base64PublicKey) if (TextUtils.isEmpty(signedData) || TextUtils.isEmpty(base64PublicKey)
|| TextUtils.isEmpty(signature) || TextUtils.isEmpty(signature)
) { ) {
BillingHelper.logWarn(TAG, "Purchase verification failed: missing data.") Timber.w("Purchase verification failed: missing data.")
return false return false
} }
@ -70,7 +67,7 @@ object Security {
* is invalid * is invalid
*/ */
@Throws(IOException::class) @Throws(IOException::class)
fun generatePublicKey(encodedPublicKey: String): PublicKey { private fun generatePublicKey(encodedPublicKey: String): PublicKey {
try { try {
val decodedKey = Base64.decode(encodedPublicKey, Base64.DEFAULT) val decodedKey = Base64.decode(encodedPublicKey, Base64.DEFAULT)
val keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM) val keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM)
@ -80,7 +77,7 @@ object Security {
throw RuntimeException(e) throw RuntimeException(e)
} catch (e: InvalidKeySpecException) { } catch (e: InvalidKeySpecException) {
val msg = "Invalid key specification: $e" val msg = "Invalid key specification: $e"
BillingHelper.logWarn(TAG, msg) Timber.w(msg)
throw IOException(msg) throw IOException(msg)
} }
@ -95,12 +92,12 @@ object Security {
* @param signature server signature * @param signature server signature
* @return true if the data and signature match * @return true if the data and signature match
*/ */
fun verify(publicKey: PublicKey, signedData: String, signature: String): Boolean { private fun verify(publicKey: PublicKey, signedData: String, signature: String): Boolean {
val signatureBytes: ByteArray val signatureBytes: ByteArray
try { try {
signatureBytes = Base64.decode(signature, Base64.DEFAULT) signatureBytes = Base64.decode(signature, Base64.DEFAULT)
} catch (e: IllegalArgumentException) { } catch (e: IllegalArgumentException) {
BillingHelper.logWarn(TAG, "Base64 decoding failed.") Timber.w("Base64 decoding failed.")
return false return false
} }
@ -109,7 +106,7 @@ object Security {
signatureAlgorithm.initVerify(publicKey) signatureAlgorithm.initVerify(publicKey)
signatureAlgorithm.update(signedData.toByteArray()) signatureAlgorithm.update(signedData.toByteArray())
if (!signatureAlgorithm.verify(signatureBytes)) { if (!signatureAlgorithm.verify(signatureBytes)) {
BillingHelper.logWarn(TAG, "Signature verification failed.") Timber.w("Signature verification failed.")
return false return false
} }
return true return true
@ -117,9 +114,9 @@ object Security {
// "RSA" is guaranteed to be available. // "RSA" is guaranteed to be available.
throw RuntimeException(e) throw RuntimeException(e)
} catch (e: InvalidKeyException) { } catch (e: InvalidKeyException) {
BillingHelper.logWarn(TAG, "Invalid key specification.") Timber.w("Invalid key specification.")
} catch (e: SignatureException) { } catch (e: SignatureException) {
BillingHelper.logWarn(TAG, "Signature exception.") Timber.w("Signature exception.")
} }
return false return false

@ -191,7 +191,7 @@ open class CSVImporter(istream: InputStream) {
fun save(realm: Realm) { fun save(realm: Realm) {
this.parser.close() this.parser.close()
// realm.refresh() realm.refresh()
this.currentDescriptor?.save(realm) this.currentDescriptor?.save(realm)
this.usedDescriptors.forEach { descriptor -> this.usedDescriptors.forEach { descriptor ->
@ -201,7 +201,7 @@ open class CSVImporter(istream: InputStream) {
fun cancel(realm: Realm) { fun cancel(realm: Realm) {
this.parser.close() this.parser.close()
// realm.refresh() realm.refresh()
this.currentDescriptor?.cancel(realm) this.currentDescriptor?.cancel(realm)
this.usedDescriptors.forEach { descriptor -> this.usedDescriptors.forEach { descriptor ->

@ -806,5 +806,6 @@
<string name="video_export_started">We\'ll send you a notification when your file is available. Expect approximately one minute!</string> <string name="video_export_started">We\'ll send you a notification when your file is available. Expect approximately one minute!</string>
<string name="show_villain_cards">Show villain cards</string> <string name="show_villain_cards">Show villain cards</string>
<string name="please_save_hand_history">Please save before sharing</string> <string name="please_save_hand_history">Please save before sharing</string>
<string name="contact_support">It looks like there is an issue here. Please contact the support to get help.</string>
</resources> </resources>

Loading…
Cancel
Save