From 8cc26f47b18bbc1944404d3378b885742a1d7586 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Sun, 2 Jul 2017 11:57:57 -0700 Subject: Remap billing functionality --- .../com/pitchedapps/frost/SettingsActivity.kt | 8 +- .../kotlin/com/pitchedapps/frost/settings/Feed.kt | 2 +- .../kotlin/com/pitchedapps/frost/utils/Prefs.kt | 4 + .../kotlin/com/pitchedapps/frost/utils/iab/IAB.kt | 114 +++++++++++++++------ .../com/pitchedapps/frost/utils/iab/IABDialogs.kt | 31 ++++-- 5 files changed, 113 insertions(+), 46 deletions(-) (limited to 'app/src/main/kotlin/com') diff --git a/app/src/main/kotlin/com/pitchedapps/frost/SettingsActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/SettingsActivity.kt index 0d084b51..0cdc5631 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/SettingsActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/SettingsActivity.kt @@ -24,14 +24,16 @@ import com.pitchedapps.frost.utils.iab.* class SettingsActivity : KPrefActivity(), IabBroadcastReceiver.IabBroadcastListener { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - if (!(IAB.helper?.handleActivityResult(requestCode, resultCode, data) ?: false)) + if (!IAB.handleActivityResult(requestCode, resultCode, data)) { super.onActivityResult(requestCode, resultCode, data) - adapter.notifyDataSetChanged() + adapter.notifyDataSetChanged() + } } override fun receivedBroadcast() { L.d("IAB broadcast") + adapter.notifyDataSetChanged() } override fun kPrefCoreAttributes(): CoreAttributeContract.() -> Unit = { @@ -68,7 +70,7 @@ class SettingsActivity : KPrefActivity(), IabBroadcastReceiver.IabBroadcastListe plainText(R.string.restore_purchases) { descRes = R.string.restore_purchases iicon = GoogleMaterial.Icon.gmd_refresh - onClick = { this@SettingsActivity.restorePurchases(); true } + onClick = { _, _,_ -> this@SettingsActivity.restorePurchases(); true } } plainText(R.string.about_frost) { diff --git a/app/src/main/kotlin/com/pitchedapps/frost/settings/Feed.kt b/app/src/main/kotlin/com/pitchedapps/frost/settings/Feed.kt index 60b0d2e9..b8bfb086 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/settings/Feed.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/settings/Feed.kt @@ -22,7 +22,7 @@ fun SettingsActivity.getFeedPrefs(): KPrefAdapterBuilder.() -> Unit = { title(R.string.newsfeed_sort) items(FeedSort.values().map { string(it.textRes) }) itemsCallbackSingleChoice(item.pref, { - _, _, which, text -> + _, _, which, _ -> if (item.pref != which) { item.pref = which shouldRestartMain() diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/Prefs.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/Prefs.kt index cc815c55..e51bb0cb 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/utils/Prefs.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/Prefs.kt @@ -87,6 +87,10 @@ object Prefs : KPref() { //check if this is the first time launching the web overlay; show snackbar if true var firstWebOverlay: Boolean by kpref("first_web_overlay", true) + /** + * Cache like value to determine if user has or had pro + * In most cases, [com.pitchedapps.frost.utils.iab.IS_FROST_PRO] should be looked at instead + */ var previouslyPro: Boolean by kpref("previously_pro", false) var debugPro: Boolean by kpref("debug_pro", false) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/iab/IAB.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/iab/IAB.kt index e047e2ff..b9213ae7 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/utils/iab/IAB.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/iab/IAB.kt @@ -2,6 +2,7 @@ package com.pitchedapps.frost.utils.iab import android.app.Activity import android.content.Context +import android.content.Intent import ca.allanwang.kau.utils.isFromGooglePlay import com.crashlytics.android.answers.PurchaseEvent import com.pitchedapps.frost.BuildConfig @@ -9,39 +10,63 @@ import com.pitchedapps.frost.SettingsActivity import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.Prefs import com.pitchedapps.frost.utils.frostAnswers -import com.pitchedapps.frost.utils.frostAnswersCustom /** * Created by Allan Wang on 2017-06-23. */ object IAB { - var helper: IabHelper? = null + private var helper: IabHelper? = null - fun setupAsync(activity: Activity) { - if (helper == null) { - L.d("IAB setup async") - if (!activity.isFromGooglePlay && !BuildConfig.DEBUG) return L.d("IAB not from google play") - try { - helper = IabHelper(activity.applicationContext, PUBLIC_BILLING_KEY) - helper!!.enableDebugLogging(BuildConfig.DEBUG, "Frost:") - helper!!.startSetup { - result -> - L.d("IAB result ${result.message}") - if (!result.isSuccess) L.eThrow("IAB Setup error: $result") + /** + * Wrapper function to ensure that the helper exists before executing a command + * + * [mustHavePlayStore] decides if dialogs should be shown if play store errors occur + * + * [onStart] should return true if we wish to dispose the helper after the operation + * and false otherwise + * + */ + operator fun invoke(activity: Activity, mustHavePlayStore: Boolean = true, onStart: (helper: IabHelper) -> Boolean) { + with(activity) { + if (helper?.mDisposed ?: true) { + helper = null + L.d("IAB setup async") + if (!isFrostPlay) { + if (mustHavePlayStore) playStoreNotFound() + return } - } catch (e: Exception) { - L.e(e, "IAB error") - activity.playStoreNoLongerPro() - } + try { + helper = IabHelper(applicationContext, PUBLIC_BILLING_KEY) + helper!!.enableDebugLogging(BuildConfig.DEBUG, "Frost:") + helper!!.startSetup { + result -> + if (result.isSuccess) { + if (onStart(helper!!)) + helper!!.disposeWhenFinished() + } else if (mustHavePlayStore) + activity.playStoreGenericError("Setup error: ${result.response} ${result.message}") + } + } catch (e: Exception) { + L.e(e, "IAB error") + if (mustHavePlayStore) + playStoreGenericError(null) + } + } else if (onStart(helper!!)) + helper!!.disposeWhenFinished() } } + fun handleActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean + = helper?.handleActivityResult(requestCode, resultCode, data) ?: false + + /** - * If user has pro, check if it's valid and destroy the helper + * Call this after any execution to dispose the helper */ - fun validatePro(activity: Activity) { - + fun dispose() { + helper?.disposeWhenFinished() + helper = null } } @@ -54,29 +79,51 @@ private val Context.isFrostPlay: Boolean get() = isFromGooglePlay || BuildConfig.DEBUG fun SettingsActivity.restorePurchases() { + validatePro(this) +} +/** + * If user has pro, check if it's valid and destroy the helper + */ +fun Activity.validatePro(activity: Activity) { + IAB(activity, Prefs.previouslyPro) { //if pro, ensure that it is in inventory; if not, check quietly if it exists + helper -> + with(activity) { + helper.queryInventoryAsync { + res, inv -> + if (res.isFailure) return@queryInventoryAsync playStoreGenericError("Query res error") + if (inv?.getSkuDetails(FROST_PRO) != null) { + //owns pro + if (!Prefs.previouslyPro) + playStoreFoundPro() + } else if (Prefs.previouslyPro) { + //doesn't own pro but has it + playStoreNoLongerPro() + } + } + } + true + } } -fun Activity.openPlayProPurchase(code: Int) = openPlayPurchase(FROST_PRO, code) { - Prefs.previouslyPro = true +fun Activity.openPlayProPurchase(code: Int) { + if (!IS_FROST_PRO) + playStoreProNotAvailable() + else openPlayPurchase(FROST_PRO, code) { + Prefs.previouslyPro = true + } } fun Activity.openPlayPurchase(key: String, code: Int, onSuccess: (key: String) -> Unit) { L.d("Open play purchase $key $code") - if (!isFrostPlay) return playStoreNotFound() - frostAnswersCustom("PLAY_PURCHASE") { - putCustomAttribute("Key", key) - } - L.d("IAB flag end async") - IAB.helper?.flagEndAsync() ?: return playStoreGenericError("Null flag end async") - L.d("IAB query inv async") - try { - IAB.helper!!.queryInventoryAsync { + IAB(this, true) { + helper -> + helper.queryInventoryAsync { res, inv -> if (res.isFailure) return@queryInventoryAsync playStoreGenericError("Query res error") if (inv?.getSkuDetails(key) != null) return@queryInventoryAsync playStoreAlreadyPurchased(key) L.d("IAB: inventory ${inv.allOwnedSkus}") - IAB.helper!!.launchPurchaseFlow(this@openPlayPurchase, key, code) { + helper.launchPurchaseFlow(this@openPlayPurchase, key, code) { result, _ -> if (result.isSuccess) { onSuccess(key) @@ -90,7 +137,6 @@ fun Activity.openPlayPurchase(key: String, code: Int, onSuccess: (key: String) - } } } - } catch(e: IabHelper.IabAsyncInProgressException) { - L.e(e, "IAB query dup") + false } } \ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/iab/IABDialogs.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/iab/IABDialogs.kt index 08958480..330680f6 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/utils/iab/IABDialogs.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/iab/IABDialogs.kt @@ -18,16 +18,18 @@ private fun playStoreLog(text: String) { L.e(Throwable(text), "Play Store Exception") } +/** + * Properly restart an activity + */ private fun Activity.playRestart() { - if (this is MainActivity) restart() - else if (this is SettingsActivity) { + if (this is SettingsActivity) { setResult(MainActivity.REQUEST_RESTART) finish() - } + } else restart() } + fun Activity.playStoreNoLongerPro() { - if (!Prefs.previouslyPro) return //never pro to begin with Prefs.previouslyPro = false playStoreLog("No Longer Pro") materialDialogThemed { @@ -40,6 +42,19 @@ fun Activity.playStoreNoLongerPro() { } } +fun Activity.playStoreFoundPro() { + Prefs.previouslyPro = true + L.d("Found pro") + materialDialogThemed { + title(R.string.found_pro) + content(R.string.found_pro_desc) + positiveText(R.string.reload) + dismissListener { + this@playStoreFoundPro.playRestart() + } + } +} + fun Activity.playStoreNotFound() { L.d("Play store not found") materialDialogThemed { @@ -52,18 +67,18 @@ fun Activity.playStoreNotFound() { } fun Activity.playStoreProNotAvailable() { - playStoreLog("Pro found; store not available") + playStoreLog("Pro query; store not available") materialDialogThemed { title(R.string.uh_oh) - content(R.string.play_store_not_found) + content(R.string.play_store_not_found_pro_query) positiveText(R.string.kau_ok) neutralText(R.string.kau_play_store) onNeutral { _, _ -> startPlayStoreLink(R.string.play_store_package_id) } } } -fun Activity.playStoreGenericError(text: String = "Store generic error") { - playStoreLog("IAB: $text") +fun Activity.playStoreGenericError(text: String? = "Store generic error") { + if (text != null) playStoreLog("IAB: $text") materialDialogThemed { title(R.string.uh_oh) content(R.string.play_store_billing_error) -- cgit v1.2.3