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 --- app/build.gradle | 11 +- .../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 ++++-- app/src/main/res/values/strings.xml | 5 +- app/src/main/res/xml/changelog.xml | 11 ++ gradle.properties | 1 - 9 files changed, 134 insertions(+), 53 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index c8f40eb8..b64e5a74 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -11,6 +11,11 @@ android { compileSdkVersion Integer.parseInt(project.TARGET_SDK) buildToolsVersion project.BUILD_TOOLS + androidGitVersion { + codeFormat = 'MMNNPP' + prefix 'v' + } + defaultConfig { applicationId "${project.APP_GROUP}." + project.APP_ID.toLowerCase() minSdkVersion Integer.parseInt(project.MIN_SDK) @@ -128,8 +133,4 @@ dependencies { compile("com.crashlytics.sdk.android:crashlytics:${CRASHLYTICS}@aar") { transitive = true; } -} - -androidGitVersion { - prefix 'v' -} +} \ No newline at end of file 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) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d6c8afd7..58c879d2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -51,12 +51,15 @@ Uh Oh Reload It seems like you are a pro user, but we couldn\'t find your purchasing info. If this error persists, please try clearing the Play Store cache and reinstalling the app. - This is a pro feature, but this app doesn\'t seem to be installed from the Play Store. Please reinstall if this is an issue. + This app doesn\'t seem to be installed from the Play Store. Please reinstall if this is an issue. + This is a pro feature, but this app doesn\'t seem to be installed from the Play Store. Please reinstall if this is an issue. Something went wrong. Please try again later. Thank you! Thank you for your support! Enjoy the pro version. Already Purchased Looks like you\'ve already purchased %s. Enjoy! + Found Frost Pro! + Looks like you have frost pro! We\'ll reload the app so you can enjoy the awesome features! diff --git a/app/src/main/res/xml/changelog.xml b/app/src/main/res/xml/changelog.xml index e189577e..e916cdd5 100644 --- a/app/src/main/res/xml/changelog.xml +++ b/app/src/main/res/xml/changelog.xml @@ -6,6 +6,17 @@ --> + + + + + + + + + + + diff --git a/gradle.properties b/gradle.properties index 91f22293..54baaf69 100644 --- a/gradle.properties +++ b/gradle.properties @@ -16,7 +16,6 @@ APP_GROUP=com.pitchedapps MIN_SDK=21 TARGET_SDK=26 BUILD_TOOLS=26.0.0 -VERSION_CODE=11 KAU=e1e3b37000 KOTLIN=1.1.3 -- cgit v1.2.3