From 1811531d6934f4eadfca70b30afa2dac76400bef Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Fri, 30 Jun 2017 20:32:33 -0700 Subject: Test more billing --- app/build.gradle | 6 +- .../kotlin/com/pitchedapps/frost/AboutActivity.kt | 82 ++++++++++++++++++++-- .../main/kotlin/com/pitchedapps/frost/FrostApp.kt | 15 ++-- .../kotlin/com/pitchedapps/frost/MainActivity.kt | 23 ++++++ .../com/pitchedapps/frost/settings/Experimental.kt | 10 +++ .../kotlin/com/pitchedapps/frost/utils/iab/IAB.kt | 63 +++++++---------- .../com/pitchedapps/frost/utils/iab/IABDialogs.kt | 50 +++++++++++++ app/src/main/res/layout/item_about_links.xml | 33 +++++++++ app/src/main/res/values/ids.xml | 1 + app/src/main/res/values/strings.xml | 2 + app/src/main/res/values/strings_preferences | 2 + gradle.properties | 4 +- 12 files changed, 237 insertions(+), 54 deletions(-) create mode 100644 app/src/main/kotlin/com/pitchedapps/frost/utils/iab/IABDialogs.kt create mode 100644 app/src/main/res/layout/item_about_links.xml diff --git a/app/build.gradle b/app/build.gradle index 9b8446ff..f72c9096 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -53,8 +53,8 @@ android { resValue "string", "frost_web", "Frost Web Test" } release { - minifyEnabled true - shrinkResources true + minifyEnabled false + shrinkResources false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' resValue "string", "app_name", "Frost" resValue "string", "frost_web", "Frost Web" @@ -121,6 +121,8 @@ dependencies { compile "com.jude:swipebackhelper:${SWIPE_BACK}" + compile 'com.android.billingclient:billing:dp-1' + compile("com.crashlytics.sdk.android:crashlytics:${CRASHLYTICS}@aar") { transitive = true; } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/AboutActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/AboutActivity.kt index 67ce8f7c..b9bb52d8 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/AboutActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/AboutActivity.kt @@ -1,14 +1,25 @@ package com.pitchedapps.frost +import android.graphics.Color +import android.support.constraint.ConstraintLayout +import android.support.v7.widget.RecyclerView +import android.view.View +import android.widget.ImageView import ca.allanwang.kau.about.AboutActivityBase import ca.allanwang.kau.adapters.FastItemThemedAdapter -import ca.allanwang.kau.iitems.CardIItem -import ca.allanwang.kau.logging.KL -import ca.allanwang.kau.utils.isColorVisibleOn -import ca.allanwang.kau.utils.withMinAlpha +import ca.allanwang.kau.adapters.ThemableIItem +import ca.allanwang.kau.adapters.ThemableIItemDelegate +import ca.allanwang.kau.iitems.LibraryIItem +import ca.allanwang.kau.utils.* +import ca.allanwang.kau.views.createSimpleRippleDrawable import com.mikepenz.aboutlibraries.Libs import com.mikepenz.aboutlibraries.entity.Library +import com.mikepenz.aboutlibraries.entity.License +import com.mikepenz.community_material_typeface_library.CommunityMaterial import com.mikepenz.fastadapter.IItem +import com.mikepenz.fastadapter.items.AbstractItem +import com.mikepenz.google_material_typeface_library.GoogleMaterial +import com.mikepenz.iconics.typeface.IIcon import com.pitchedapps.frost.utils.Prefs @@ -51,8 +62,65 @@ class AboutActivity : AboutActivityBase(R.string::class.java, configBuilder = { } override fun postInflateMainPage(adapter: FastItemThemedAdapter>) { - adapter.add(CardIItem { - descRes = R.string.frost_description - }) + /** + * Frost may not be a library but we're conveying the same info + */ + val frost = Library().apply { + libraryName = string(R.string.app_name) + author = "Pitched Apps" + libraryWebsite = "https://github.com/AllanWang/Frost-for-Facebook" + isOpenSource = true + libraryDescription = string(R.string.frost_description) + libraryVersion = BuildConfig.VERSION_NAME + license = License().apply { + licenseName = "GNU GPL v3" + licenseWebsite = "https://www.gnu.org/licenses/gpl-3.0.en.html" + } + } + adapter.add(LibraryIItem(frost)).add(AboutLinks()) + + } + + class AboutLinks : AbstractItem(), ThemableIItem by ThemableIItemDelegate() { + override fun getViewHolder(v: View): ViewHolder = ViewHolder(v) + + override fun getType(): Int = R.id.item_about_links + + override fun getLayoutRes(): Int = R.layout.item_about_links + + override fun bindView(holder: ViewHolder, payloads: MutableList?) { + super.bindView(holder, payloads) + with(holder) { + bindIconColor(rate, github) + bindBackgroundColor(container) + } + } + + fun theme(vararg images: ImageView) { + val ripple = createSimpleRippleDrawable(accentColor!!, Color.TRANSPARENT) + images.forEach { + it.background = ripple + it.drawable.setTint(textColor!!) + } + } + + class ViewHolder(v: View) : RecyclerView.ViewHolder(v) { + + val container: ConstraintLayout by bindView(R.id.about_icons_container) + val rate: ImageView by bindView(R.id.about_rate) + val github: ImageView by bindView(R.id.about_github) + + init { + val c = itemView.context + setup(rate, GoogleMaterial.Icon.gmd_star, { c.startPlayStoreLink(R.string.play_store_package_id) }) + setup(github, CommunityMaterial.Icon.cmd_github_circle, { c.startLink("https://github.com/AllanWang/Frost-for-Facebook") }) + } + + fun setup(image: ImageView, icon: IIcon, onClick: () -> Unit) { + image.setImageDrawable(icon.toDrawable(itemView.context, 32)) + image.setOnClickListener({ onClick() }) + } + + } } } \ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt b/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt index a221859f..4dce7d4a 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt @@ -12,8 +12,8 @@ import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader import com.mikepenz.materialdrawer.util.DrawerImageLoader import com.pitchedapps.frost.facebook.FbCookie import com.pitchedapps.frost.utils.CrashReportingTree -import com.pitchedapps.frost.utils.iab.IAB import com.pitchedapps.frost.utils.Prefs +import com.pitchedapps.frost.utils.iab.IAB import com.raizlabs.android.dbflow.config.FlowConfig import com.raizlabs.android.dbflow.config.FlowManager import io.fabric.sdk.android.Fabric @@ -36,11 +36,7 @@ class FrostApp : Application() { override fun onCreate() { FlowManager.init(FlowConfig.Builder(this).build()) Prefs.initialize(this, "${BuildConfig.APPLICATION_ID}.prefs") - FbCookie() - if (Prefs.installDate == -1L) Prefs.installDate = System.currentTimeMillis() - if (Prefs.identifier == -1) Prefs.identifier = Random().nextInt(Int.MAX_VALUE) - Prefs.lastLaunch = System.currentTimeMillis() -// if (LeakCanary.isInAnalyzerProcess(this)) return + // if (LeakCanary.isInAnalyzerProcess(this)) return // refWatcher = LeakCanary.install(this) if (BuildConfig.DEBUG) { Timber.plant(DebugTree()) @@ -50,10 +46,15 @@ class FrostApp : Application() { Crashlytics.setUserIdentifier(Prefs.frostId) Timber.plant(CrashReportingTree()) } + FbCookie() + if (Prefs.installDate == -1L) Prefs.installDate = System.currentTimeMillis() + if (Prefs.identifier == -1) Prefs.identifier = Random().nextInt(Int.MAX_VALUE) + Prefs.lastLaunch = System.currentTimeMillis() + + super.onCreate() - IAB.setupAsync(this) //Drawer profile loading logic DrawerImageLoader.init(object : AbstractDrawerImageLoader() { override fun set(imageView: ImageView, uri: Uri, placeholder: Drawable, tag: String) { diff --git a/app/src/main/kotlin/com/pitchedapps/frost/MainActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/MainActivity.kt index dcb68696..9094c58e 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/MainActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/MainActivity.kt @@ -1,7 +1,11 @@ package com.pitchedapps.frost +import android.app.AlarmManager +import android.app.PendingIntent +import android.content.Context import android.content.Intent import android.graphics.drawable.ColorDrawable +import android.os.Build import android.os.Bundle import android.support.annotation.StringRes import android.support.design.widget.* @@ -39,6 +43,7 @@ import com.pitchedapps.frost.facebook.FbTab import com.pitchedapps.frost.facebook.PROFILE_PICTURE_URL import com.pitchedapps.frost.fragments.WebFragment import com.pitchedapps.frost.utils.* +import com.pitchedapps.frost.utils.iab.IAB import com.pitchedapps.frost.views.BadgedIcon import com.pitchedapps.frost.web.FrostWebViewSearch import io.reactivex.android.schedulers.AndroidSchedulers @@ -84,6 +89,7 @@ class MainActivity : BaseActivity(), FrostWebViewSearch.SearchContract { const val REQUEST_REFRESH = 80808 const val REQUEST_NAV = 10101 const val REQUEST_SEARCH = 70707 + const val REQUEST_RESTART_APPLICATION = 60606 } override fun onCreate(savedInstanceState: Bundle?) { @@ -365,6 +371,18 @@ class MainActivity : BaseActivity(), FrostWebViewSearch.SearchContract { REQUEST_REFRESH -> webFragmentObservable.onNext(FRAGMENT_REFRESH) REQUEST_NAV -> frostNavigationBar() REQUEST_SEARCH -> invalidateOptionsMenu() + REQUEST_RESTART_APPLICATION -> { //completely restart application + val intent = packageManager.getLaunchIntentForPackage(packageName) + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK) + val pending = PendingIntent.getActivity(this, 666, intent, PendingIntent.FLAG_CANCEL_CURRENT) + val alarm = getSystemService(Context.ALARM_SERVICE) as AlarmManager + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) + alarm.setExactAndAllowWhileIdle(AlarmManager.RTC, System.currentTimeMillis() + 100, pending) + else + alarm.setExact(AlarmManager.RTC, System.currentTimeMillis() + 100, pending) + finish() + System.exit(0) + } } } } @@ -374,6 +392,11 @@ class MainActivity : BaseActivity(), FrostWebViewSearch.SearchContract { FbCookie.switchBackUser { } } + override fun onStart() { + super.onStart() + IAB.setupAsync(this) + } + override fun onBackPressed() { if (searchView?.onBackPressed() ?: false) return if (currentFragment.onBackPressed()) return diff --git a/app/src/main/kotlin/com/pitchedapps/frost/settings/Experimental.kt b/app/src/main/kotlin/com/pitchedapps/frost/settings/Experimental.kt index 054b3669..fcb22320 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/settings/Experimental.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/settings/Experimental.kt @@ -22,4 +22,14 @@ fun SettingsActivity.getExperimentalPrefs(): KPrefAdapterBuilder.() -> Unit = { checkbox(R.string.verbose_logging, { Prefs.verboseLogging }, { Prefs.verboseLogging = it }) { descRes = R.string.verbose_logging_desc } + + plainText(R.string.restart_frost) { + descRes = R.string.restart_frost_desc + onClick = { + _, _, _ -> + setResult(MainActivity.REQUEST_RESTART_APPLICATION) + finish() + true + } + } } \ No newline at end of file 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 19b9b6f7..0fd10c5b 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 @@ -16,17 +16,20 @@ object IAB { var helper: IabHelper? = null - fun setupAsync(context: Context) { - if (!context.isFromGooglePlay) return + 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(context.applicationContext, PUBLIC_BILLING_KEY) + helper = IabHelper(activity.applicationContext, PUBLIC_BILLING_KEY) helper!!.startSetup { result -> + L.d("IAB result ${result.message}") if (!result.isSuccess) L.eThrow("IAB Setup error: $result") } } catch (e: Exception) { L.e(e, "IAB error") + activity.playStoreNoLongerPro() } } } @@ -35,7 +38,7 @@ object IAB { private const val FROST_PRO = "frost_pro" val IS_FROST_PRO: Boolean - get() = (BuildConfig.DEBUG && Prefs.debugPro) || (IAB.helper?.queryInventory()?.getSkuDetails(FROST_PRO) != null) + get() = (BuildConfig.DEBUG && Prefs.debugPro) || Prefs.previouslyPro private fun Context.checkFromPlay(): Boolean { val isPlay = isFromGooglePlay || BuildConfig.DEBUG @@ -52,44 +55,32 @@ private fun Context.checkFromPlay(): Boolean { fun Activity.openPlayProPurchase(code: Int) = openPlayPurchase(FROST_PRO, code) fun Activity.openPlayPurchase(key: String, code: Int) { + L.d("Open play purchase $key $code") if (!checkFromPlay()) return frostAnswersCustom("PLAY_PURCHASE") { putCustomAttribute("Key", key) } - IAB.helper?.flagEndAsync() ?: playStoreErrorDialog() - IAB.helper?.queryInventoryAsync { + L.d("IAB flag end async") + IAB.helper?.flagEndAsync() ?: return playStoreGenericError("Null flag end async") + L.d("IAB query inv async") + IAB.helper!!.queryInventoryAsync { res, inv -> - if (res.isFailure) { - L.e("IAB error: ${res.message}") - playStoreErrorDialog() - } else if (inv == null) { - playStoreErrorDialog("Empty inventory") - } else { - val donation = inv.getSkuDetails(key) - if (donation != null) { - IAB.helper?.launchPurchaseFlow(this@openPlayPurchase, donation.sku, code) { - result, _ -> - if (result.isSuccess) materialDialogThemed { - title(R.string.play_thank_you) - content(R.string.play_purchased_pro) - positiveText(R.string.kau_ok) - } else playStoreErrorDialog("Result: ${result.message}") - frostAnswers { - logPurchase(PurchaseEvent() - .putItemId(key) - .putSuccess(result.isSuccess)) - } - } ?: playStoreErrorDialog("Launch Purchase Flow") + if (res.isFailure) return@queryInventoryAsync playStoreGenericError("Query res error") + if (inv == null) return@queryInventoryAsync playStoreGenericError("Empty inventory") + L.d("IAB: inventory ${inv.allOwnedSkus}") + val donation = inv.getSkuDetails(key) ?: return@queryInventoryAsync playStoreGenericError("Donation null") + IAB.helper!!.launchPurchaseFlow(this@openPlayPurchase, donation.sku, code) { + result, _ -> + if (result.isSuccess) materialDialogThemed { + title(R.string.play_thank_you) + content(R.string.play_purchased_pro) + positiveText(R.string.kau_ok) + } else playStoreGenericError("Result: ${result.message}") + frostAnswers { + logPurchase(PurchaseEvent() + .putItemId(key) + .putSuccess(result.isSuccess)) } } } -} - -private fun Context.playStoreErrorDialog(s: String = "Play Store Error") { - materialDialogThemed { - title(R.string.uh_oh) - content(R.string.play_store_billing_error) - positiveText(R.string.kau_ok) - } - L.e(Throwable(s), "Play Store Error") } \ 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 new file mode 100644 index 00000000..e855138f --- /dev/null +++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/iab/IABDialogs.kt @@ -0,0 +1,50 @@ +package com.pitchedapps.frost.utils.iab + +import android.app.Activity +import ca.allanwang.kau.utils.restart +import ca.allanwang.kau.utils.startPlayStoreLink +import com.pitchedapps.frost.R +import com.pitchedapps.frost.utils.L +import com.pitchedapps.frost.utils.Prefs +import com.pitchedapps.frost.utils.materialDialogThemed + +/** + * Created by Allan Wang on 2017-06-30. + */ +private fun playStoreLog(text: String) { + L.e(Throwable(text), "Play Store Exception") +} + +fun Activity.playStoreNoLongerPro() { + if (!Prefs.previouslyPro) return //never pro to begin with + Prefs.previouslyPro = false + playStoreLog("No Longer Pro") + materialDialogThemed { + title(R.string.uh_oh) + content(R.string.play_store_not_pro) + positiveText(R.string.reload) + dismissListener { + this@playStoreNoLongerPro.restart() + } + } +} + +fun Activity.playStoreNotAvailable() { + playStoreLog("Store not available") + materialDialogThemed { + title(R.string.uh_oh) + content(R.string.play_store_not_found) + 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") + materialDialogThemed { + title(R.string.uh_oh) + content(R.string.play_store_billing_error) + positiveText(R.string.kau_ok) + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/item_about_links.xml b/app/src/main/res/layout/item_about_links.xml new file mode 100644 index 00000000..db831835 --- /dev/null +++ b/app/src/main/res/layout/item_about_links.xml @@ -0,0 +1,33 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/ids.xml b/app/src/main/res/values/ids.xml index 9ce1b4e3..a0bb0a87 100644 --- a/app/src/main/res/values/ids.xml +++ b/app/src/main/res/values/ids.xml @@ -2,4 +2,5 @@ + \ 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 5e4fe3ba..ec7a6cf6 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -49,6 +49,8 @@ Pro Features Custom [Pro] 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. Something went wrong. Please try again later. Thank you! diff --git a/app/src/main/res/values/strings_preferences b/app/src/main/res/values/strings_preferences index afa41943..1c20b810 100644 --- a/app/src/main/res/values/strings_preferences +++ b/app/src/main/res/values/strings_preferences @@ -54,5 +54,7 @@ Enable the search bar instead of a search overlay Verbose Logging Enable verbose logging to help with crash reports. Logging will only be sent once an error is encountered, so repeat the issue to notify the dev. + Restart Frost + Crashlytics will only submit logs when a crash occurs or if errors are found and the app is restarted. Clicking here will restart the app and flush whatever issues are currently found. \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 249c9a9c..548ad31b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -16,7 +16,7 @@ APP_GROUP=com.pitchedapps MIN_SDK=21 TARGET_SDK=26 BUILD_TOOLS=26.0.0 -VERSION_CODE=7 +VERSION_CODE=9 VERSION_NAME=1.0 KAU=e1e3b37000 @@ -31,4 +31,4 @@ DBFLOW=4.0.4 PAPER_PARCEL=2.0.1 SWIPE_BACK=3.1.2 CRASHLYTICS=2.6.8 -LEAK_CANARY=1.5.1 +LEAK_CANARY=1.5.1 \ No newline at end of file -- cgit v1.2.3