mirror of
https://github.com/AllanWang/Frost-for-Facebook.git
synced 2024-11-08 12:02:33 +01:00
Test new billing logic (#86)
* Add lint * Add new libs * Update libs and add friends tab * Aggressively hide nonrecent posts * Update dependencies * Add php to most recents * Add full size image downloader * Fix css cleaner * Fix notification and circle * Bring back regex * Update kau, optimize imports, and remove string ambiguity * Bring back anjlab iab and move to alpha * Create initial billing test
This commit is contained in:
parent
37a9f9057d
commit
1388240656
@ -10,7 +10,7 @@ apply plugin: 'com.github.triplet.play'
|
||||
|
||||
play {
|
||||
jsonFile = file('../files/gplay-keys.json')
|
||||
track = 'beta'
|
||||
track = 'alpha'
|
||||
errorOnSizeLimit = true
|
||||
uploadImages = false
|
||||
untrackOld = true
|
||||
@ -130,11 +130,10 @@ dependencies {
|
||||
|
||||
compile "ca.allanwang.kau:about:$KAU"
|
||||
compile "ca.allanwang.kau:colorpicker:$KAU"
|
||||
// compile "ca.allanwang.kau:imagepicker:$KAU"
|
||||
compile "ca.allanwang.kau:imagepicker:$KAU"
|
||||
compile "ca.allanwang.kau:kpref-activity:$KAU"
|
||||
compile "ca.allanwang.kau:searchview:$KAU"
|
||||
|
||||
compile "org.jetbrains.kotlin:kotlin-stdlib:${KOTLIN}"
|
||||
testCompile "org.jetbrains.kotlin:kotlin-test-junit:${KOTLIN}"
|
||||
|
||||
debugCompile "com.squareup.leakcanary:leakcanary-android:${LEAK_CANARY}"
|
||||
@ -155,8 +154,7 @@ dependencies {
|
||||
|
||||
compile "com.squareup.okhttp3:okhttp:${OKHTTP}"
|
||||
|
||||
compile "com.github.bumptech.glide:glide:${GLIDE}"
|
||||
kapt "com.github.bumptech.glide:compiler:${GLIDE}"
|
||||
compile "com.anjlab.android.iab.v3:library:${IAB}"
|
||||
|
||||
// compile("com.mikepenz:materialdrawer:${MATERIAL_DRAWER}@aar") {
|
||||
// transitive = true
|
||||
|
@ -117,7 +117,7 @@
|
||||
android:theme="@style/FrostTheme.Settings" />
|
||||
<activity
|
||||
android:name=".activities.AboutActivity"
|
||||
android:theme="@style/Kau.Translucent.About" />
|
||||
android:theme="@style/Kau.About" />
|
||||
<activity
|
||||
android:name=".activities.ImageActivity"
|
||||
android:theme="@style/FrostTheme.Overlay.Fade" />
|
||||
|
@ -4,16 +4,15 @@ import android.app.Application
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.net.Uri
|
||||
import android.widget.ImageView
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.request.RequestOptions
|
||||
import com.bumptech.glide.signature.ApplicationVersionSignature
|
||||
import com.crashlytics.android.Crashlytics
|
||||
import com.crashlytics.android.answers.Answers
|
||||
import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader
|
||||
import com.mikepenz.materialdrawer.util.DrawerImageLoader
|
||||
import com.pitchedapps.frost.BuildConfig
|
||||
import com.pitchedapps.frost.facebook.FbCookie
|
||||
import com.pitchedapps.frost.utils.CrashReportingTree
|
||||
import com.pitchedapps.frost.utils.GlideApp
|
||||
import com.pitchedapps.frost.utils.Prefs
|
||||
import com.pitchedapps.frost.utils.Showcase
|
||||
import com.raizlabs.android.dbflow.config.FlowConfig
|
||||
@ -65,8 +64,8 @@ class FrostApp : Application() {
|
||||
DrawerImageLoader.init(object : AbstractDrawerImageLoader() {
|
||||
override fun set(imageView: ImageView, uri: Uri, placeholder: Drawable, tag: String) {
|
||||
val c = imageView.context
|
||||
val old = GlideApp.with(c).load(uri).apply(RequestOptions().placeholder(placeholder))
|
||||
GlideApp.with(c).load(uri).apply(RequestOptions().signature(ApplicationVersionSignature.obtain(c)))
|
||||
val old = Glide.with(c).load(uri).apply(RequestOptions().placeholder(placeholder))
|
||||
Glide.with(c).load(uri).apply(RequestOptions().signature(ApplicationVersionSignature.obtain(c)))
|
||||
.thumbnail(old).into(imageView)
|
||||
}
|
||||
})
|
||||
|
@ -1,7 +1,5 @@
|
||||
package com.pitchedapps.frost.activities
|
||||
|
||||
import android.animation.ValueAnimator
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.Bitmap
|
||||
@ -21,6 +19,7 @@ import ca.allanwang.kau.permissions.PERMISSION_WRITE_EXTERNAL_STORAGE
|
||||
import ca.allanwang.kau.permissions.kauOnRequestPermissionsResult
|
||||
import ca.allanwang.kau.permissions.kauRequestPermissions
|
||||
import ca.allanwang.kau.utils.*
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.request.target.BaseTarget
|
||||
import com.bumptech.glide.request.target.SizeReadyCallback
|
||||
import com.bumptech.glide.request.target.Target
|
||||
@ -31,7 +30,10 @@ import com.mikepenz.google_material_typeface_library.GoogleMaterial
|
||||
import com.mikepenz.iconics.typeface.IIcon
|
||||
import com.pitchedapps.frost.BuildConfig
|
||||
import com.pitchedapps.frost.R
|
||||
import com.pitchedapps.frost.utils.*
|
||||
import com.pitchedapps.frost.utils.ARG_IMAGE_URL
|
||||
import com.pitchedapps.frost.utils.ARG_TEXT
|
||||
import com.pitchedapps.frost.utils.L
|
||||
import com.pitchedapps.frost.utils.Prefs
|
||||
import com.sothree.slidinguppanel.SlidingUpPanelLayout
|
||||
import org.jetbrains.anko.doAsync
|
||||
import org.jetbrains.anko.uiThread
|
||||
@ -82,7 +84,8 @@ class ImageActivity : AppCompatActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(if (!text.isNullOrBlank()) R.layout.activity_image else R.layout.activity_image_textless)
|
||||
val layout = if (!text.isNullOrBlank()) R.layout.activity_image else R.layout.activity_image_textless
|
||||
setContentView(layout)
|
||||
container.setBackgroundColor(Prefs.bgColor.withMinAlpha(222))
|
||||
caption?.setTextColor(Prefs.textColor)
|
||||
caption?.setBackgroundColor(Prefs.bgColor.colorToForeground(0.2f).withAlpha(255))
|
||||
@ -104,7 +107,7 @@ class ImageActivity : AppCompatActivity() {
|
||||
imageCallback(null, false)
|
||||
}
|
||||
})
|
||||
GlideApp.with(this).asBitmap().load(imageUrl).into(PhotoTarget(this::imageCallback))
|
||||
Glide.with(this).asBitmap().load(imageUrl).into(PhotoTarget(this::imageCallback))
|
||||
}
|
||||
|
||||
/**
|
||||
@ -196,7 +199,8 @@ class ImageActivity : AppCompatActivity() {
|
||||
} finally {
|
||||
L.d("Download image async finished: $success")
|
||||
uiThread {
|
||||
snackbar(if (success) R.string.image_download_success else R.string.image_download_fail)
|
||||
val text = if (success) R.string.image_download_success else R.string.image_download_fail
|
||||
snackbar(text)
|
||||
if (success) {
|
||||
deleteTempFile()
|
||||
fabAction = FabStates.SHARE
|
||||
|
@ -9,7 +9,10 @@ import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.support.annotation.StringRes
|
||||
import android.support.design.widget.*
|
||||
import android.support.design.widget.AppBarLayout
|
||||
import android.support.design.widget.CoordinatorLayout
|
||||
import android.support.design.widget.FloatingActionButton
|
||||
import android.support.design.widget.TabLayout
|
||||
import android.support.v4.app.ActivityOptionsCompat
|
||||
import android.support.v4.app.Fragment
|
||||
import android.support.v4.app.FragmentManager
|
||||
@ -52,7 +55,8 @@ 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.validatePro
|
||||
import com.pitchedapps.frost.utils.iab.FrostBilling
|
||||
import com.pitchedapps.frost.utils.iab.IABMain
|
||||
import com.pitchedapps.frost.views.BadgedIcon
|
||||
import com.pitchedapps.frost.views.FrostViewPager
|
||||
import com.pitchedapps.frost.web.SearchWebView
|
||||
@ -64,7 +68,8 @@ import org.jsoup.Jsoup
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class MainActivity : BaseActivity(), SearchWebView.SearchContract,
|
||||
ActivityWebContract, FileChooserContract by FileChooserDelegate() {
|
||||
ActivityWebContract, FileChooserContract by FileChooserDelegate(),
|
||||
FrostBilling by IABMain() {
|
||||
|
||||
lateinit var adapter: SectionsPagerAdapter
|
||||
val toolbar: Toolbar by bindView(R.id.toolbar)
|
||||
@ -97,12 +102,12 @@ class MainActivity : BaseActivity(), SearchWebView.SearchContract,
|
||||
* Possible responses from the SettingsActivity
|
||||
* after the configurations have changed
|
||||
*/
|
||||
const val REQUEST_RESTART = 90909
|
||||
const val REQUEST_REFRESH = 80808
|
||||
const val REQUEST_WEB_ZOOM = 50505
|
||||
const val REQUEST_NAV = 10101
|
||||
const val REQUEST_SEARCH = 70707
|
||||
const val REQUEST_RESTART_APPLICATION = 60606
|
||||
const val REQUEST_RESTART_APPLICATION = 1 shl 1
|
||||
const val REQUEST_RESTART = 1 shl 2
|
||||
const val REQUEST_REFRESH = 1 shl 3
|
||||
const val REQUEST_WEB_ZOOM = 1 shl 4
|
||||
const val REQUEST_NAV = 1 shl 5
|
||||
const val REQUEST_SEARCH = 1 shl 6
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
@ -149,12 +154,12 @@ class MainActivity : BaseActivity(), SearchWebView.SearchContract,
|
||||
viewPager.post { webFragmentObservable.onNext(0); lastPosition = 0 } //trigger hook so title is set
|
||||
setupDrawer(savedInstanceState)
|
||||
setupTabs()
|
||||
fab.setOnClickListener { view ->
|
||||
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
|
||||
.setAction("Action", null).show()
|
||||
}
|
||||
// fab.setOnClickListener { view ->
|
||||
// Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
|
||||
// .setAction("Action", null).show()
|
||||
// }
|
||||
setFrostColors(toolbar, themeWindow = false, headers = arrayOf(tabs, appBar), backgrounds = arrayOf(viewPager))
|
||||
validatePro()
|
||||
onCreateBilling()
|
||||
}
|
||||
|
||||
fun tabsForEachView(action: (position: Int, view: BadgedIcon) -> Unit) {
|
||||
@ -394,26 +399,28 @@ class MainActivity : BaseActivity(), SearchWebView.SearchContract,
|
||||
if (onActivityResultWeb(requestCode, resultCode, data)) return
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
if (requestCode == ACTIVITY_SETTINGS) {
|
||||
when (resultCode) {
|
||||
REQUEST_RESTART -> restart()
|
||||
REQUEST_REFRESH -> webFragmentObservable.onNext(WebFragment.REQUEST_REFRESH)
|
||||
REQUEST_NAV -> frostNavigationBar()
|
||||
REQUEST_WEB_ZOOM -> webFragmentObservable.onNext(WebFragment.REQUEST_TEXT_ZOOM)
|
||||
REQUEST_SEARCH -> invalidateOptionsMenu()
|
||||
REQUEST_RESTART_APPLICATION -> { //completely restart application
|
||||
L.d("Restart Application Requested")
|
||||
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)
|
||||
}
|
||||
if (resultCode and REQUEST_RESTART_APPLICATION > 0) { //completely restart application
|
||||
L.d("Restart Application Requested")
|
||||
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)
|
||||
return
|
||||
}
|
||||
if (resultCode and REQUEST_RESTART > 0) return restart()
|
||||
/*
|
||||
* These results can be stacked
|
||||
*/
|
||||
if (resultCode and REQUEST_REFRESH > 0) webFragmentObservable.onNext(WebFragment.REQUEST_REFRESH)
|
||||
if (resultCode and REQUEST_NAV > 0) frostNavigationBar()
|
||||
if (resultCode and REQUEST_WEB_ZOOM > 0) webFragmentObservable.onNext(WebFragment.REQUEST_TEXT_ZOOM)
|
||||
if (resultCode and REQUEST_SEARCH > 0) invalidateOptionsMenu()
|
||||
}
|
||||
}
|
||||
|
||||
@ -435,6 +442,11 @@ class MainActivity : BaseActivity(), SearchWebView.SearchContract,
|
||||
super.onStart()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
onDestroyBilling()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
if (searchView?.onBackPressed() ?: false) return
|
||||
if (currentFragment.onBackPressed()) return
|
||||
|
@ -17,27 +17,23 @@ import com.pitchedapps.frost.BuildConfig
|
||||
import com.pitchedapps.frost.R
|
||||
import com.pitchedapps.frost.settings.*
|
||||
import com.pitchedapps.frost.utils.*
|
||||
import com.pitchedapps.frost.utils.iab.*
|
||||
import com.pitchedapps.frost.utils.iab.FrostBilling
|
||||
import com.pitchedapps.frost.utils.iab.IABSettings
|
||||
import com.pitchedapps.frost.utils.iab.IS_FROST_PRO
|
||||
|
||||
|
||||
/**
|
||||
* Created by Allan Wang on 2017-06-06.
|
||||
*/
|
||||
class SettingsActivity : KPrefActivity(), IabBroadcastReceiver.IabBroadcastListener {
|
||||
class SettingsActivity : KPrefActivity(), FrostBilling by IABSettings() {
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
if (!IAB.handleActivityResult(requestCode, resultCode, data)) {
|
||||
if (!onActivityResultBilling(requestCode, resultCode, data)) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun receivedBroadcast() {
|
||||
L.d("IAB broadcast")
|
||||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun kPrefCoreAttributes(): CoreAttributeContract.() -> Unit = {
|
||||
textColor = { Prefs.textColor }
|
||||
accentColor = { Prefs.accentColor }
|
||||
@ -72,7 +68,7 @@ class SettingsActivity : KPrefActivity(), IabBroadcastReceiver.IabBroadcastListe
|
||||
plainText(R.string.restore_purchases) {
|
||||
descRes = R.string.restore_purchases_desc
|
||||
iicon = GoogleMaterial.Icon.gmd_refresh
|
||||
onClick = { _, _, _ -> this@SettingsActivity.restorePurchases(); true }
|
||||
onClick = { _, _, _ -> restorePurchases(false); true }
|
||||
}
|
||||
|
||||
plainText(R.string.about_frost) {
|
||||
@ -86,7 +82,7 @@ class SettingsActivity : KPrefActivity(), IabBroadcastReceiver.IabBroadcastListe
|
||||
}
|
||||
|
||||
fun KPrefItemBase.BaseContract<*>.dependsOnPro() {
|
||||
onDisabledClick = { _, _, _ -> openPlayProPurchase(0); true }
|
||||
onDisabledClick = { _, _, _ -> purchasePro(); true }
|
||||
enabler = { IS_FROST_PRO }
|
||||
}
|
||||
|
||||
@ -99,6 +95,7 @@ class SettingsActivity : KPrefActivity(), IabBroadcastReceiver.IabBroadcastListe
|
||||
super.onCreate(savedInstanceState)
|
||||
animate = Prefs.animate
|
||||
themeExterior(false)
|
||||
onCreateBilling()
|
||||
}
|
||||
|
||||
fun themeExterior(animate: Boolean = true) {
|
||||
@ -139,7 +136,7 @@ class SettingsActivity : KPrefActivity(), IabBroadcastReceiver.IabBroadcastListe
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
IAB.dispose()
|
||||
onDestroyBilling()
|
||||
super.onDestroy()
|
||||
}
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
package com.pitchedapps.frost.injectors
|
||||
|
||||
import android.webkit.WebView
|
||||
import com.pitchedapps.frost.utils.L
|
||||
|
||||
/**
|
||||
* Created by Allan Wang on 2017-05-31.
|
||||
|
@ -14,15 +14,19 @@ import android.support.v4.app.NotificationManagerCompat
|
||||
import ca.allanwang.kau.utils.color
|
||||
import ca.allanwang.kau.utils.dpToPx
|
||||
import ca.allanwang.kau.utils.string
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.request.target.SimpleTarget
|
||||
import com.bumptech.glide.request.transition.Transition
|
||||
import com.pitchedapps.frost.BuildConfig
|
||||
import com.pitchedapps.frost.activities.FrostWebActivity
|
||||
import com.pitchedapps.frost.R
|
||||
import com.pitchedapps.frost.activities.FrostWebActivity
|
||||
import com.pitchedapps.frost.dbflow.CookieModel
|
||||
import com.pitchedapps.frost.dbflow.fetchUsername
|
||||
import com.pitchedapps.frost.facebook.FB_URL_BASE
|
||||
import com.pitchedapps.frost.utils.*
|
||||
import com.pitchedapps.frost.utils.ARG_USER_ID
|
||||
import com.pitchedapps.frost.utils.L
|
||||
import com.pitchedapps.frost.utils.Prefs
|
||||
import com.pitchedapps.frost.utils.withRoundIcon
|
||||
import org.jetbrains.anko.runOnUiThread
|
||||
|
||||
/**
|
||||
@ -100,7 +104,7 @@ data class NotificationContent(val data: CookieModel,
|
||||
|
||||
if (profileUrl.isNotBlank()) {
|
||||
context.runOnUiThread {
|
||||
GlideApp.with(context)
|
||||
Glide.with(context)
|
||||
.asBitmap()
|
||||
.load(profileUrl)
|
||||
.withRoundIcon()
|
||||
|
@ -17,9 +17,7 @@ import com.pitchedapps.frost.facebook.USER_AGENT_BASIC
|
||||
import com.pitchedapps.frost.utils.L
|
||||
import com.pitchedapps.frost.utils.Prefs
|
||||
import com.pitchedapps.frost.utils.frostAnswersCustom
|
||||
import com.pitchedapps.frost.web.MessageWebView
|
||||
import org.jetbrains.anko.doAsync
|
||||
import org.jetbrains.anko.uiThread
|
||||
import org.jsoup.Jsoup
|
||||
import org.jsoup.nodes.Element
|
||||
import java.util.concurrent.Future
|
||||
|
@ -5,13 +5,12 @@ import ca.allanwang.kau.kpref.activity.items.KPrefColorPicker
|
||||
import ca.allanwang.kau.kpref.activity.items.KPrefSeekbar
|
||||
import ca.allanwang.kau.ui.views.RippleCanvas
|
||||
import ca.allanwang.kau.utils.string
|
||||
import com.pitchedapps.frost.activities.MainActivity
|
||||
import com.pitchedapps.frost.R
|
||||
import com.pitchedapps.frost.activities.MainActivity
|
||||
import com.pitchedapps.frost.activities.SettingsActivity
|
||||
import com.pitchedapps.frost.injectors.CssAssets
|
||||
import com.pitchedapps.frost.utils.*
|
||||
import com.pitchedapps.frost.utils.iab.IS_FROST_PRO
|
||||
import com.pitchedapps.frost.utils.iab.openPlayProPurchase
|
||||
import com.pitchedapps.frost.views.KPrefTextSeekbar
|
||||
|
||||
/**
|
||||
@ -33,7 +32,7 @@ fun SettingsActivity.getAppearancePrefs(): KPrefAdapterBuilder.() -> Unit = {
|
||||
_, _, which, text ->
|
||||
if (item.pref != which) {
|
||||
if (which == Theme.CUSTOM.ordinal && !IS_FROST_PRO) {
|
||||
openPlayProPurchase(9)
|
||||
purchasePro()
|
||||
return@itemsCallbackSingleChoice true
|
||||
}
|
||||
item.pref = which
|
||||
|
@ -1,8 +1,8 @@
|
||||
package com.pitchedapps.frost.settings
|
||||
|
||||
import ca.allanwang.kau.kpref.activity.KPrefAdapterBuilder
|
||||
import com.pitchedapps.frost.activities.MainActivity
|
||||
import com.pitchedapps.frost.R
|
||||
import com.pitchedapps.frost.activities.MainActivity
|
||||
import com.pitchedapps.frost.activities.SettingsActivity
|
||||
import com.pitchedapps.frost.utils.Prefs
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
package com.pitchedapps.frost.settings
|
||||
|
||||
import ca.allanwang.kau.kpref.activity.KPrefAdapterBuilder
|
||||
import com.pitchedapps.frost.activities.MainActivity
|
||||
import com.pitchedapps.frost.R
|
||||
import com.pitchedapps.frost.activities.MainActivity
|
||||
import com.pitchedapps.frost.activities.SettingsActivity
|
||||
import com.pitchedapps.frost.utils.Prefs
|
||||
import com.pitchedapps.frost.utils.Showcase
|
||||
|
@ -2,8 +2,8 @@ package com.pitchedapps.frost.settings
|
||||
|
||||
import ca.allanwang.kau.kpref.activity.KPrefAdapterBuilder
|
||||
import ca.allanwang.kau.utils.string
|
||||
import com.pitchedapps.frost.activities.MainActivity
|
||||
import com.pitchedapps.frost.R
|
||||
import com.pitchedapps.frost.activities.MainActivity
|
||||
import com.pitchedapps.frost.activities.SettingsActivity
|
||||
import com.pitchedapps.frost.facebook.FeedSort
|
||||
import com.pitchedapps.frost.utils.Prefs
|
||||
|
@ -65,7 +65,8 @@ fun SettingsActivity.getNotificationPrefs(): KPrefAdapterBuilder.() -> Unit = {
|
||||
descRes = R.string.notification_fetch_now_desc
|
||||
onClick = {
|
||||
_, _, _ ->
|
||||
snackbar(if (fetchNotifications()) R.string.notification_fetch_success else R.string.notification_fetch_fail)
|
||||
val text = if (fetchNotifications()) R.string.notification_fetch_success else R.string.notification_fetch_fail
|
||||
snackbar(text)
|
||||
true
|
||||
}
|
||||
}
|
||||
|
@ -8,18 +8,13 @@ import android.support.annotation.StringRes
|
||||
import android.support.design.internal.SnackbarContentLayout
|
||||
import android.support.design.widget.Snackbar
|
||||
import android.support.v7.widget.Toolbar
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.TextView
|
||||
import ca.allanwang.kau.utils.*
|
||||
import com.afollestad.materialdialogs.MaterialDialog
|
||||
import com.bumptech.glide.GlideBuilder
|
||||
import com.bumptech.glide.RequestBuilder
|
||||
import com.bumptech.glide.annotation.GlideExtension
|
||||
import com.bumptech.glide.annotation.GlideModule
|
||||
import com.bumptech.glide.load.resource.bitmap.CircleCrop
|
||||
import com.bumptech.glide.module.AppGlideModule
|
||||
import com.bumptech.glide.request.RequestOptions
|
||||
import com.crashlytics.android.answers.Answers
|
||||
import com.crashlytics.android.answers.CustomEvent
|
||||
@ -42,9 +37,6 @@ const val ARG_USER_ID = "arg_user_id"
|
||||
const val ARG_IMAGE_URL = "arg_image_url"
|
||||
const val ARG_TEXT = "arg_text"
|
||||
|
||||
@GlideModule
|
||||
class FrostGlideModule : AppGlideModule()
|
||||
|
||||
fun Context.launchNewTask(clazz: Class<out Activity>, cookieList: ArrayList<CookieModel> = arrayListOf(), clearStack: Boolean = false) {
|
||||
startActivity(clazz, clearStack, intentBuilder = {
|
||||
putParcelableArrayListExtra(EXTRA_COOKIES, cookieList)
|
||||
|
@ -5,8 +5,8 @@ import ca.allanwang.kau.utils.copyToClipboard
|
||||
import ca.allanwang.kau.utils.shareText
|
||||
import ca.allanwang.kau.utils.string
|
||||
import ca.allanwang.kau.utils.toast
|
||||
import com.pitchedapps.frost.activities.MainActivity
|
||||
import com.pitchedapps.frost.R
|
||||
import com.pitchedapps.frost.activities.MainActivity
|
||||
|
||||
/**
|
||||
* Created by Allan Wang on 2017-07-07.
|
||||
|
@ -1,226 +0,0 @@
|
||||
package com.pitchedapps.frost.utils.iab
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.support.design.widget.Snackbar
|
||||
import ca.allanwang.kau.utils.isFromGooglePlay
|
||||
import ca.allanwang.kau.utils.snackbar
|
||||
import com.crashlytics.android.answers.PurchaseEvent
|
||||
import com.pitchedapps.frost.BuildConfig
|
||||
import com.pitchedapps.frost.R
|
||||
import com.pitchedapps.frost.activities.SettingsActivity
|
||||
import com.pitchedapps.frost.utils.*
|
||||
|
||||
/**
|
||||
* Created by Allan Wang on 2017-06-23.
|
||||
*
|
||||
* Helper singleton to handle all billing related queries
|
||||
* NOTE
|
||||
* Make sure you call [IAB.dispose] once an operation is done to release the resources
|
||||
* Also make sure that it is called on the very LAST operation if there are a list of async calls.
|
||||
* Otherwise the helper will be prematurely disposed
|
||||
*
|
||||
* For the most part, billing is handled in the [SettingsActivity] and will be disposed when it is destroyed
|
||||
* It may also be handled elsewhere when validating purchases, so those calls should dispose themselves
|
||||
*/
|
||||
object IAB {
|
||||
|
||||
private var helper: IabHelper? = null
|
||||
|
||||
/**
|
||||
* Wrapper function to ensure that the helper exists before executing a command
|
||||
*
|
||||
* [mustHavePlayStore] decides if dialogs should be shown if play store errors occur
|
||||
*
|
||||
*/
|
||||
operator fun invoke(activity: Activity,
|
||||
mustHavePlayStore: Boolean = true,
|
||||
userRequest: Boolean = true,
|
||||
onFailed: () -> Unit = {},
|
||||
onStart: (helper: IabHelper) -> Unit) {
|
||||
with(activity) {
|
||||
if (isInProgress) {
|
||||
if (userRequest) snackbar(R.string.iab_still_in_progress, Snackbar.LENGTH_LONG)
|
||||
L.d("Play Store IAB in progress")
|
||||
} else if (helper?.disposed ?: true) {
|
||||
helper = null
|
||||
L.d("Play Store IAB setup async")
|
||||
if (!isFrostPlay) {
|
||||
if (mustHavePlayStore) playStoreNotFound()
|
||||
onFailed()
|
||||
return
|
||||
}
|
||||
try {
|
||||
helper = IabHelper(applicationContext, PUBLIC_BILLING_KEY)
|
||||
helper!!.enableDebugLogging(BuildConfig.DEBUG || Prefs.verboseLogging, "Frost:")
|
||||
helper!!.startSetup {
|
||||
result ->
|
||||
L.d("Play Store IAB setup finished; ${result.isSuccess}")
|
||||
if (result.isSuccess) {
|
||||
L.d("Play Store IAB setup success")
|
||||
onStart(helper!!)
|
||||
} else {
|
||||
L.d("Play Store IAB setup fail")
|
||||
if (mustHavePlayStore)
|
||||
activity.playStoreGenericError("Setup error: ${result.response} ${result.message}")
|
||||
onFailed()
|
||||
IAB.dispose()
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
L.e(e, "Play Store IAB error")
|
||||
if (mustHavePlayStore)
|
||||
playStoreGenericError(null)
|
||||
onFailed()
|
||||
IAB.dispose()
|
||||
}
|
||||
} else onStart(helper!!)
|
||||
}
|
||||
}
|
||||
|
||||
fun handleActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean
|
||||
= helper?.handleActivityResult(requestCode, resultCode, data) ?: false
|
||||
|
||||
/**
|
||||
* Call this after any execution to dispose the helper
|
||||
*/
|
||||
fun dispose() {
|
||||
synchronized(this) {
|
||||
L.d("Play Store IAB dispose")
|
||||
helper?.disposeWhenFinished()
|
||||
helper = null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispose given helper and check if it matches with our own helper
|
||||
*/
|
||||
fun dispose(helper: IabHelper) {
|
||||
synchronized(this) {
|
||||
L.d("Play Store IAB helper dispose")
|
||||
helper.disposeWhenFinished()
|
||||
if (IAB.helper?.disposed ?: true)
|
||||
this.helper = null
|
||||
}
|
||||
}
|
||||
|
||||
val isInProgress: Boolean
|
||||
get() = helper?.mAsyncInProgress ?: false
|
||||
}
|
||||
|
||||
private const val FROST_PRO = "frost_pro"
|
||||
|
||||
private val IabHelper.disposed: Boolean
|
||||
get() = mDisposed || mDisposeAfterAsync
|
||||
|
||||
val IS_FROST_PRO: Boolean
|
||||
get() = (BuildConfig.DEBUG && Prefs.debugPro) || Prefs.pro
|
||||
|
||||
private val Context.isFrostPlay: Boolean
|
||||
get() = isFromGooglePlay || BuildConfig.DEBUG
|
||||
|
||||
fun SettingsActivity.restorePurchases() {
|
||||
//like validate, but with a snackbar and without other prompts
|
||||
val restore = container.snackbar(R.string.restoring_purchases, Snackbar.LENGTH_INDEFINITE)
|
||||
restore.setAction(R.string.kau_close) { restore.dismiss() }
|
||||
//called if inventory is not properly retrieved
|
||||
val reset = {
|
||||
L.d("Play Store Restore reset")
|
||||
if (Prefs.pro) {
|
||||
Prefs.pro = false
|
||||
Prefs.theme = Theme.DEFAULT.ordinal
|
||||
}
|
||||
finishRestore(restore, false)
|
||||
}
|
||||
getInventory(false, true, reset) {
|
||||
inv, _ ->
|
||||
val proSku = inv.hasPurchase(FROST_PRO)
|
||||
Prefs.pro = proSku
|
||||
L.d("Play Store Restore found: ${Prefs.pro}")
|
||||
finishRestore(restore, Prefs.pro)
|
||||
}
|
||||
}
|
||||
|
||||
private fun SettingsActivity.finishRestore(snackbar: Snackbar, hasPro: Boolean) {
|
||||
snackbar.dismiss()
|
||||
materialDialogThemed {
|
||||
title(R.string.purchases_restored)
|
||||
content(if (hasPro) R.string.purchases_restored_with_pro else R.string.purchases_restored_without_pro)
|
||||
positiveText(R.string.reload)
|
||||
dismissListener { adapter.notifyAdapterDataSetChanged() }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If user has pro, check if it's valid and destroy the helper
|
||||
* If cache matches result, it finishes silently
|
||||
*/
|
||||
fun Activity.validatePro() {
|
||||
L.d("Play Store Validate pro")
|
||||
try {
|
||||
getInventory(Prefs.pro, false, { if (Prefs.pro) playStoreNoLongerPro() }) {
|
||||
inv, helper ->
|
||||
val proSku = inv.hasPurchase(FROST_PRO)
|
||||
L.d("Play Store Validation finished: ${Prefs.pro} should be $proSku")
|
||||
if (!proSku && Prefs.pro) playStoreNoLongerPro()
|
||||
else if (proSku && !Prefs.pro) playStoreFoundPro()
|
||||
IAB.dispose(helper)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
L.e(e, "Play store validation exception")
|
||||
IAB.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
fun Activity.getInventory(
|
||||
mustHavePlayStore: Boolean = true,
|
||||
userRequest: Boolean = true,
|
||||
onFailed: () -> Unit = {},
|
||||
onSuccess: (inv: Inventory, helper: IabHelper) -> Unit) {
|
||||
IAB(this, mustHavePlayStore, userRequest, onFailed) {
|
||||
helper ->
|
||||
helper.queryInventoryAsync {
|
||||
res, inv ->
|
||||
L.d("Play Store Inventory query finished")
|
||||
if (res.isFailure || inv == null) {
|
||||
L.e("Play Store Res error ${res.message}")
|
||||
onFailed()
|
||||
} else onSuccess(inv, helper)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Activity.openPlayProPurchase(code: Int) {
|
||||
if (!isFrostPlay)
|
||||
playStoreProNotAvailable()
|
||||
else openPlayPurchase(FROST_PRO, code) {
|
||||
Prefs.pro = true
|
||||
}
|
||||
}
|
||||
|
||||
fun Activity.openPlayPurchase(key: String, code: Int, onSuccess: (key: String) -> Unit) {
|
||||
L.d("Play Store open purchase $key $code")
|
||||
getInventory(true, true, { playStoreGenericError("Query res error") }) {
|
||||
inv, helper ->
|
||||
if (inv.hasPurchase(key)) {
|
||||
playStoreAlreadyPurchased(key)
|
||||
onSuccess(key)
|
||||
return@getInventory
|
||||
}
|
||||
L.d("IAB: inventory ${inv.allOwnedSkus}")
|
||||
helper.launchPurchaseFlow(this@openPlayPurchase, key, code) {
|
||||
result, _ ->
|
||||
if (result.isSuccess) {
|
||||
onSuccess(key)
|
||||
playStorePurchasedSuccessfully(key)
|
||||
}
|
||||
frostAnswers {
|
||||
logPurchase(PurchaseEvent()
|
||||
.putItemId(key)
|
||||
.putCustomAttribute("result", result.message)
|
||||
.putSuccess(result.isSuccess))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
139
app/src/main/kotlin/com/pitchedapps/frost/utils/iab/IABBinder.kt
Normal file
139
app/src/main/kotlin/com/pitchedapps/frost/utils/iab/IABBinder.kt
Normal file
@ -0,0 +1,139 @@
|
||||
package com.pitchedapps.frost.utils.iab
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import com.anjlab.android.iab.v3.BillingProcessor
|
||||
import com.anjlab.android.iab.v3.TransactionDetails
|
||||
import com.crashlytics.android.answers.PurchaseEvent
|
||||
import com.pitchedapps.frost.BuildConfig
|
||||
import com.pitchedapps.frost.utils.L
|
||||
import com.pitchedapps.frost.utils.Prefs
|
||||
import com.pitchedapps.frost.utils.frostAnswers
|
||||
import org.jetbrains.anko.doAsync
|
||||
import org.jetbrains.anko.uiThread
|
||||
|
||||
/**
|
||||
* Created by Allan Wang on 2017-07-22.
|
||||
*/
|
||||
private const val FROST_PRO = "frost_pro"
|
||||
|
||||
val IS_FROST_PRO: Boolean
|
||||
get() = (BuildConfig.DEBUG && Prefs.debugPro) || Prefs.pro
|
||||
|
||||
interface FrostBilling : BillingProcessor.IBillingHandler {
|
||||
fun Activity.onCreateBilling()
|
||||
fun onDestroyBilling()
|
||||
fun purchasePro()
|
||||
fun restorePurchases(once: Boolean)
|
||||
fun onActivityResultBilling(requestCode: Int, resultCode: Int, data: Intent?): Boolean
|
||||
}
|
||||
|
||||
open class IABBinder : FrostBilling {
|
||||
|
||||
var bp: BillingProcessor? = null
|
||||
var activity: Activity? = null
|
||||
|
||||
override fun Activity.onCreateBilling() {
|
||||
bp = BillingProcessor.newBillingProcessor(this, PUBLIC_BILLING_KEY, this@IABBinder)
|
||||
activity = this
|
||||
bp!!.initialize()
|
||||
}
|
||||
|
||||
override fun onDestroyBilling() {
|
||||
bp?.release()
|
||||
bp = null
|
||||
activity = null
|
||||
}
|
||||
|
||||
override fun onBillingInitialized() {
|
||||
L.d("IAB initialized")
|
||||
}
|
||||
|
||||
override fun onPurchaseHistoryRestored() {
|
||||
L.d("IAB restored")
|
||||
}
|
||||
|
||||
override fun onProductPurchased(productId: String, details: TransactionDetails) {
|
||||
L.d("IAB $productId purchased")
|
||||
frostAnswers {
|
||||
logPurchase(PurchaseEvent()
|
||||
.putItemId(productId)
|
||||
.putSuccess(true)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBillingError(errorCode: Int, error: Throwable) {
|
||||
frostAnswers {
|
||||
logPurchase(PurchaseEvent()
|
||||
.putCustomAttribute("result", errorCode.toString())
|
||||
.putSuccess(false))
|
||||
}
|
||||
L.e(error, "IAB error $errorCode")
|
||||
}
|
||||
|
||||
override fun onActivityResultBilling(requestCode: Int, resultCode: Int, data: Intent?): Boolean
|
||||
= bp?.handleActivityResult(requestCode, resultCode, data) ?: false
|
||||
|
||||
override fun purchasePro() {
|
||||
if (bp == null) return
|
||||
if (!bp!!.isOneTimePurchaseSupported)
|
||||
activity!!.playStorePurchaseUnsupported()
|
||||
else
|
||||
bp!!.purchase(activity, FROST_PRO)
|
||||
}
|
||||
|
||||
override fun restorePurchases(once: Boolean) {
|
||||
if (bp == null) return
|
||||
doAsync {
|
||||
bp?.loadOwnedPurchasesFromGoogle()
|
||||
if (bp?.isPurchased(FROST_PRO) ?: false) {
|
||||
uiThread {
|
||||
if (Prefs.pro) activity!!.playStoreNoLongerPro()
|
||||
else if (!once) purchasePro()
|
||||
if (once) onDestroyBilling()
|
||||
}
|
||||
} else {
|
||||
uiThread {
|
||||
if (!Prefs.pro) activity!!.playStoreFoundPro()
|
||||
else if (!once) activity!!.purchaseRestored()
|
||||
if (once) onDestroyBilling()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class IABSettings : IABBinder() {
|
||||
|
||||
override fun onBillingInitialized() {
|
||||
super.onBillingInitialized()
|
||||
|
||||
}
|
||||
|
||||
override fun onPurchaseHistoryRestored() {
|
||||
super.onPurchaseHistoryRestored()
|
||||
}
|
||||
|
||||
override fun onProductPurchased(productId: String, details: TransactionDetails) {
|
||||
super.onProductPurchased(productId, details)
|
||||
}
|
||||
|
||||
override fun onBillingError(errorCode: Int, error: Throwable) {
|
||||
super.onBillingError(errorCode, error)
|
||||
activity?.playStoreGenericError(null)
|
||||
}
|
||||
}
|
||||
|
||||
class IABMain : IABBinder() {
|
||||
|
||||
override fun onBillingInitialized() {
|
||||
super.onBillingInitialized()
|
||||
restorePurchases(true)
|
||||
}
|
||||
|
||||
override fun onPurchaseHistoryRestored() {
|
||||
super.onPurchaseHistoryRestored()
|
||||
restorePurchases(true)
|
||||
}
|
||||
}
|
@ -4,8 +4,8 @@ import android.app.Activity
|
||||
import ca.allanwang.kau.utils.restart
|
||||
import ca.allanwang.kau.utils.startPlayStoreLink
|
||||
import ca.allanwang.kau.utils.string
|
||||
import com.pitchedapps.frost.activities.MainActivity
|
||||
import com.pitchedapps.frost.R
|
||||
import com.pitchedapps.frost.activities.MainActivity
|
||||
import com.pitchedapps.frost.activities.SettingsActivity
|
||||
import com.pitchedapps.frost.utils.L
|
||||
import com.pitchedapps.frost.utils.Prefs
|
||||
@ -14,6 +14,7 @@ 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")
|
||||
}
|
||||
@ -28,7 +29,6 @@ private fun Activity.playRestart() {
|
||||
} else restart()
|
||||
}
|
||||
|
||||
|
||||
fun Activity.playStoreNoLongerPro() {
|
||||
Prefs.pro = false
|
||||
playStoreLog("No Longer Pro")
|
||||
@ -55,11 +55,11 @@ fun Activity.playStoreFoundPro() {
|
||||
}
|
||||
}
|
||||
|
||||
fun Activity.playStoreNotFound() {
|
||||
fun Activity.playStorePurchaseUnsupported() {
|
||||
L.d("Play store not found")
|
||||
materialDialogThemed {
|
||||
title(R.string.uh_oh)
|
||||
content(R.string.play_store_not_found)
|
||||
content(R.string.play_store_unsupported)
|
||||
positiveText(R.string.kau_ok)
|
||||
neutralText(R.string.kau_play_store)
|
||||
onNeutral { _, _ -> startPlayStoreLink(R.string.play_store_package_id) }
|
||||
@ -107,7 +107,7 @@ fun Activity.playStorePurchasedSuccessfully(key: String) {
|
||||
}
|
||||
}
|
||||
|
||||
fun SettingsActivity.purchaseRestored() {
|
||||
fun Activity.purchaseRestored() {
|
||||
L.d("Purchase restored")
|
||||
materialDialogThemed {
|
||||
title(R.string.play_thank_you)
|
||||
|
@ -1,60 +0,0 @@
|
||||
/* Copyright (c) 2014 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.pitchedapps.frost.utils.iab;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
/**
|
||||
* Receiver for the "com.android.vending.billing.PURCHASES_UPDATED" Action
|
||||
* from the Play Store.
|
||||
*
|
||||
* <p>It is possible that an in-app item may be acquired without the
|
||||
* application calling getBuyIntent(), for example if the item can be
|
||||
* redeemed from inside the Play Store using a promotional code. If this
|
||||
* application isn't running at the time, then when it is started a call
|
||||
* to getPurchases() will be sufficient notification. However, if the
|
||||
* application is already running in the background when the item is acquired,
|
||||
* a message to this BroadcastReceiver will indicate that the an item
|
||||
* has been acquired.</p>
|
||||
*/
|
||||
public class IabBroadcastReceiver extends BroadcastReceiver {
|
||||
/**
|
||||
* Listener interface for received broadcast messages.
|
||||
*/
|
||||
public interface IabBroadcastListener {
|
||||
void receivedBroadcast();
|
||||
}
|
||||
|
||||
/**
|
||||
* The Intent action that this Receiver should filter for.
|
||||
*/
|
||||
public static final String ACTION = "com.android.vending.billing.PURCHASES_UPDATED";
|
||||
|
||||
private final IabBroadcastListener mListener;
|
||||
|
||||
public IabBroadcastReceiver(IabBroadcastListener listener) {
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (mListener != null) {
|
||||
mListener.receivedBroadcast();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
/* Copyright (c) 2012 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.pitchedapps.frost.utils.iab;
|
||||
|
||||
/**
|
||||
* Exception thrown when something went wrong with in-app billing.
|
||||
* An IabException has an associated IabResult (an error).
|
||||
* To get the IAB result that caused this exception to be thrown,
|
||||
* call {@link #getResult()}.
|
||||
*/
|
||||
public class IabException extends Exception {
|
||||
IabResult mResult;
|
||||
|
||||
public IabException(IabResult r) {
|
||||
this(r, null);
|
||||
}
|
||||
public IabException(int response, String message) {
|
||||
this(new IabResult(response, message));
|
||||
}
|
||||
public IabException(IabResult r, Exception cause) {
|
||||
super(r.getMessage(), cause);
|
||||
mResult = r;
|
||||
}
|
||||
public IabException(int response, String message, Exception cause) {
|
||||
this(new IabResult(response, message), cause);
|
||||
}
|
||||
|
||||
/** Returns the IAB result (error) that this exception signals. */
|
||||
public IabResult getResult() { return mResult; }
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,45 +0,0 @@
|
||||
/* Copyright (c) 2012 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.pitchedapps.frost.utils.iab;
|
||||
|
||||
/**
|
||||
* Represents the result of an in-app billing operation.
|
||||
* A result is composed of a response code (an integer) and possibly a
|
||||
* message (String). You can get those by calling
|
||||
* {@link #getResponse} and {@link #getMessage()}, respectively. You
|
||||
* can also inquire whether a result is a success or a failure by
|
||||
* calling {@link #isSuccess()} and {@link #isFailure()}.
|
||||
*/
|
||||
public class IabResult {
|
||||
int mResponse;
|
||||
String mMessage;
|
||||
|
||||
public IabResult(int response, String message) {
|
||||
mResponse = response;
|
||||
if (message == null || message.trim().length() == 0) {
|
||||
mMessage = IabHelper.getResponseDesc(response);
|
||||
}
|
||||
else {
|
||||
mMessage = message + " (response: " + IabHelper.getResponseDesc(response) + ")";
|
||||
}
|
||||
}
|
||||
public int getResponse() { return mResponse; }
|
||||
public String getMessage() { return mMessage; }
|
||||
public boolean isSuccess() { return mResponse == IabHelper.BILLING_RESPONSE_RESULT_OK; }
|
||||
public boolean isFailure() { return !isSuccess(); }
|
||||
public String toString() { return "IabResult: " + getMessage(); }
|
||||
}
|
||||
|
@ -1,91 +0,0 @@
|
||||
/* Copyright (c) 2012 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.pitchedapps.frost.utils.iab;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Represents a block of information about in-app items.
|
||||
* An Inventory is returned by such methods as {@link IabHelper#queryInventory}.
|
||||
*/
|
||||
public class Inventory {
|
||||
Map<String,SkuDetails> mSkuMap = new HashMap<String,SkuDetails>();
|
||||
Map<String,Purchase> mPurchaseMap = new HashMap<String,Purchase>();
|
||||
|
||||
Inventory() { }
|
||||
|
||||
/** Returns the listing details for an in-app product. */
|
||||
public SkuDetails getSkuDetails(String sku) {
|
||||
return mSkuMap.get(sku);
|
||||
}
|
||||
|
||||
/** Returns purchase information for a given product, or null if there is no purchase. */
|
||||
public Purchase getPurchase(String sku) {
|
||||
return mPurchaseMap.get(sku);
|
||||
}
|
||||
|
||||
/** Returns whether or not there exists a purchase of the given product. */
|
||||
public boolean hasPurchase(String sku) {
|
||||
return mPurchaseMap.containsKey(sku);
|
||||
}
|
||||
|
||||
/** Return whether or not details about the given product are available. */
|
||||
public boolean hasDetails(String sku) {
|
||||
return mSkuMap.containsKey(sku);
|
||||
}
|
||||
|
||||
/**
|
||||
* Erase a purchase (locally) from the inventory, given its product ID. This just
|
||||
* modifies the Inventory object locally and has no effect on the server! This is
|
||||
* useful when you have an existing Inventory object which you know to be up to date,
|
||||
* and you have just consumed an item successfully, which means that erasing its
|
||||
* purchase data from the Inventory you already have is quicker than querying for
|
||||
* a new Inventory.
|
||||
*/
|
||||
public void erasePurchase(String sku) {
|
||||
if (mPurchaseMap.containsKey(sku)) mPurchaseMap.remove(sku);
|
||||
}
|
||||
|
||||
/** Returns a list of all owned product IDs. */
|
||||
List<String> getAllOwnedSkus() {
|
||||
return new ArrayList<String>(mPurchaseMap.keySet());
|
||||
}
|
||||
|
||||
/** Returns a list of all owned product IDs of a given type */
|
||||
List<String> getAllOwnedSkus(String itemType) {
|
||||
List<String> result = new ArrayList<String>();
|
||||
for (Purchase p : mPurchaseMap.values()) {
|
||||
if (p.getItemType().equals(itemType)) result.add(p.getSku());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Returns a list of all purchases. */
|
||||
List<Purchase> getAllPurchases() {
|
||||
return new ArrayList<Purchase>(mPurchaseMap.values());
|
||||
}
|
||||
|
||||
void addSkuDetails(SkuDetails d) {
|
||||
mSkuMap.put(d.getSku(), d);
|
||||
}
|
||||
|
||||
void addPurchase(Purchase p) {
|
||||
mPurchaseMap.put(p.getSku(), p);
|
||||
}
|
||||
}
|
@ -1,66 +0,0 @@
|
||||
/* Copyright (c) 2012 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.pitchedapps.frost.utils.iab;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
* Represents an in-app billing purchase.
|
||||
*/
|
||||
public class Purchase {
|
||||
String mItemType; // ITEM_TYPE_INAPP or ITEM_TYPE_SUBS
|
||||
String mOrderId;
|
||||
String mPackageName;
|
||||
String mSku;
|
||||
long mPurchaseTime;
|
||||
int mPurchaseState;
|
||||
String mDeveloperPayload;
|
||||
String mToken;
|
||||
String mOriginalJson;
|
||||
String mSignature;
|
||||
boolean mIsAutoRenewing;
|
||||
|
||||
public Purchase(String itemType, String jsonPurchaseInfo, String signature) throws JSONException {
|
||||
mItemType = itemType;
|
||||
mOriginalJson = jsonPurchaseInfo;
|
||||
JSONObject o = new JSONObject(mOriginalJson);
|
||||
mOrderId = o.optString("orderId");
|
||||
mPackageName = o.optString("packageName");
|
||||
mSku = o.optString("productId");
|
||||
mPurchaseTime = o.optLong("purchaseTime");
|
||||
mPurchaseState = o.optInt("purchaseState");
|
||||
mDeveloperPayload = o.optString("developerPayload");
|
||||
mToken = o.optString("token", o.optString("purchaseToken"));
|
||||
mIsAutoRenewing = o.optBoolean("autoRenewing");
|
||||
mSignature = signature;
|
||||
}
|
||||
|
||||
public String getItemType() { return mItemType; }
|
||||
public String getOrderId() { return mOrderId; }
|
||||
public String getPackageName() { return mPackageName; }
|
||||
public String getSku() { return mSku; }
|
||||
public long getPurchaseTime() { return mPurchaseTime; }
|
||||
public int getPurchaseState() { return mPurchaseState; }
|
||||
public String getDeveloperPayload() { return mDeveloperPayload; }
|
||||
public String getToken() { return mToken; }
|
||||
public String getOriginalJson() { return mOriginalJson; }
|
||||
public String getSignature() { return mSignature; }
|
||||
public boolean isAutoRenewing() { return mIsAutoRenewing; }
|
||||
|
||||
@Override
|
||||
public String toString() { return "PurchaseInfo(type:" + mItemType + "):" + mOriginalJson; }
|
||||
}
|
@ -1,121 +0,0 @@
|
||||
/* Copyright (c) 2012 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.pitchedapps.frost.utils.iab;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PublicKey;
|
||||
import java.security.Signature;
|
||||
import java.security.SignatureException;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
|
||||
/**
|
||||
* Security-related methods. For a secure implementation, all of this code
|
||||
* should be implemented on a server that communicates with the
|
||||
* application on the device. For the sake of simplicity and clarity of this
|
||||
* example, this code is included here and is executed on the device. If you
|
||||
* must verify the purchases on the phone, you should obfuscate this code to
|
||||
* make it harder for an attacker to replace the code with stubs that treat all
|
||||
* purchases as verified.
|
||||
*/
|
||||
public class Security {
|
||||
private static final String TAG = "IABUtil/Security";
|
||||
|
||||
private static final String KEY_FACTORY_ALGORITHM = "RSA";
|
||||
private static final String SIGNATURE_ALGORITHM = "SHA1withRSA";
|
||||
|
||||
/**
|
||||
* Verifies that the data was signed with the given signature, and returns
|
||||
* the verified purchase. The data is in JSON format and signed
|
||||
* with a private key. The data also contains the {@link PurchaseState}
|
||||
* and product ID of the purchase.
|
||||
* @param base64PublicKey the base64-encoded public key to use for verifying.
|
||||
* @param signedData the signed JSON string (signed, not encrypted)
|
||||
* @param signature the signature for the data, signed with the private key
|
||||
*/
|
||||
public static boolean verifyPurchase(String base64PublicKey, String signedData, String signature) {
|
||||
if (TextUtils.isEmpty(signedData) || TextUtils.isEmpty(base64PublicKey) ||
|
||||
TextUtils.isEmpty(signature)) {
|
||||
Log.e(TAG, "Purchase verification failed: missing data.");
|
||||
return false;
|
||||
}
|
||||
|
||||
PublicKey key = Security.generatePublicKey(base64PublicKey);
|
||||
return Security.verify(key, signedData, signature);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a PublicKey instance from a string containing the
|
||||
* Base64-encoded public key.
|
||||
*
|
||||
* @param encodedPublicKey Base64-encoded public key
|
||||
* @throws IllegalArgumentException if encodedPublicKey is invalid
|
||||
*/
|
||||
public static PublicKey generatePublicKey(String encodedPublicKey) {
|
||||
try {
|
||||
byte[] decodedKey = Base64.decode(encodedPublicKey, Base64.DEFAULT);
|
||||
KeyFactory keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM);
|
||||
return keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey));
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (InvalidKeySpecException e) {
|
||||
Log.e(TAG, "Invalid key specification.");
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that the signature from the server matches the computed
|
||||
* signature on the data. Returns true if the data is correctly signed.
|
||||
*
|
||||
* @param publicKey public key associated with the developer account
|
||||
* @param signedData signed data from server
|
||||
* @param signature server signature
|
||||
* @return true if the data and signature match
|
||||
*/
|
||||
public static boolean verify(PublicKey publicKey, String signedData, String signature) {
|
||||
byte[] signatureBytes;
|
||||
try {
|
||||
signatureBytes = Base64.decode(signature, Base64.DEFAULT);
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.e(TAG, "Base64 decoding failed.");
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
Signature sig = Signature.getInstance(SIGNATURE_ALGORITHM);
|
||||
sig.initVerify(publicKey);
|
||||
sig.update(signedData.getBytes());
|
||||
if (!sig.verify(signatureBytes)) {
|
||||
Log.e(TAG, "Signature verification failed.");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
Log.e(TAG, "NoSuchAlgorithmException.");
|
||||
} catch (InvalidKeyException e) {
|
||||
Log.e(TAG, "Invalid key specification.");
|
||||
} catch (SignatureException e) {
|
||||
Log.e(TAG, "Signature exception.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@ -1,64 +0,0 @@
|
||||
/* Copyright (c) 2012 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.pitchedapps.frost.utils.iab;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
* Represents an in-app product's listing details.
|
||||
*/
|
||||
public class SkuDetails {
|
||||
private final String mItemType;
|
||||
private final String mSku;
|
||||
private final String mType;
|
||||
private final String mPrice;
|
||||
private final long mPriceAmountMicros;
|
||||
private final String mPriceCurrencyCode;
|
||||
private final String mTitle;
|
||||
private final String mDescription;
|
||||
private final String mJson;
|
||||
|
||||
public SkuDetails(String jsonSkuDetails) throws JSONException {
|
||||
this(IabHelper.ITEM_TYPE_INAPP, jsonSkuDetails);
|
||||
}
|
||||
|
||||
public SkuDetails(String itemType, String jsonSkuDetails) throws JSONException {
|
||||
mItemType = itemType;
|
||||
mJson = jsonSkuDetails;
|
||||
JSONObject o = new JSONObject(mJson);
|
||||
mSku = o.optString("productId");
|
||||
mType = o.optString("type");
|
||||
mPrice = o.optString("price");
|
||||
mPriceAmountMicros = o.optLong("price_amount_micros");
|
||||
mPriceCurrencyCode = o.optString("price_currency_code");
|
||||
mTitle = o.optString("title");
|
||||
mDescription = o.optString("description");
|
||||
}
|
||||
|
||||
public String getSku() { return mSku; }
|
||||
public String getType() { return mType; }
|
||||
public String getPrice() { return mPrice; }
|
||||
public long getPriceAmountMicros() { return mPriceAmountMicros; }
|
||||
public String getPriceCurrencyCode() { return mPriceCurrencyCode; }
|
||||
public String getTitle() { return mTitle; }
|
||||
public String getDescription() { return mDescription; }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SkuDetails:" + mJson;
|
||||
}
|
||||
}
|
@ -12,9 +12,7 @@ import ca.allanwang.kau.utils.toDrawable
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.load.DataSource
|
||||
import com.bumptech.glide.load.engine.GlideException
|
||||
import com.bumptech.glide.load.resource.bitmap.CircleCrop
|
||||
import com.bumptech.glide.request.RequestListener
|
||||
import com.bumptech.glide.request.RequestOptions
|
||||
import com.bumptech.glide.request.target.Target
|
||||
import com.mikepenz.google_material_typeface_library.GoogleMaterial
|
||||
import com.pitchedapps.frost.R
|
||||
|
@ -4,7 +4,6 @@ import android.content.Context
|
||||
import android.support.v4.view.ViewPager
|
||||
import android.util.AttributeSet
|
||||
import android.view.MotionEvent
|
||||
import com.pitchedapps.frost.utils.L
|
||||
import com.pitchedapps.frost.utils.Prefs
|
||||
|
||||
/**
|
||||
|
@ -2,8 +2,6 @@ package com.pitchedapps.frost.web
|
||||
|
||||
import android.content.Context
|
||||
import android.webkit.JavascriptInterface
|
||||
import ca.allanwang.kau.utils.startActivity
|
||||
import com.pitchedapps.frost.activities.ImageActivity
|
||||
import com.pitchedapps.frost.activities.MainActivity
|
||||
import com.pitchedapps.frost.dbflow.CookieModel
|
||||
import com.pitchedapps.frost.facebook.formattedFbUrl
|
||||
|
@ -4,7 +4,10 @@ import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.webkit.*
|
||||
import android.webkit.ConsoleMessage
|
||||
import android.webkit.CookieManager
|
||||
import android.webkit.WebChromeClient
|
||||
import android.webkit.WebView
|
||||
import ca.allanwang.kau.utils.fadeIn
|
||||
import com.pitchedapps.frost.R
|
||||
import com.pitchedapps.frost.dbflow.CookieModel
|
||||
|
@ -2,7 +2,6 @@ package com.pitchedapps.frost.web
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import android.webkit.JavascriptInterface
|
||||
import android.webkit.WebView
|
||||
import ca.allanwang.kau.searchview.SearchItem
|
||||
|
@ -54,7 +54,7 @@
|
||||
<string name="uh_oh">Uh Oh</string>
|
||||
<string name="reload">Reload</string>
|
||||
<string name="play_store_not_pro">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.</string>
|
||||
<string name="play_store_not_found">This app doesn\'t seem to be installed from the Play Store. Please reinstall if this is an issue.</string>
|
||||
<string name="play_store_unsupported">It seems like app version can\'t purchase pro. Please reinstall from the play store if this is a persisting issue.</string>
|
||||
<string name="play_store_not_found_pro_query">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.</string>
|
||||
<string name="play_store_billing_error">Something went wrong. Please try again later.</string>
|
||||
<string name="play_thank_you">Thank you!</string>
|
||||
|
@ -9,7 +9,7 @@
|
||||
-->
|
||||
|
||||
<version title="Beta Updates" />
|
||||
|
||||
|
||||
<item text="Fix regex bug for some devices" />
|
||||
<item text="Fix notification text" />
|
||||
<item text="Update round icons" />
|
||||
@ -25,7 +25,7 @@
|
||||
<item text="Start filtering out unnecessary loads" />
|
||||
<item text="Fix notification duplicates" />
|
||||
<item text="Fix long pressing album images" />
|
||||
<item text="Add friend request tab in nav bar" />
|
||||
<item text="Add friend request tab in nav bar" />
|
||||
<item text="Aggressively filter nonrecent posts in recents mode" />
|
||||
<item text="Add download option for full sized images" />
|
||||
<item text="Fix rounded icons" />
|
||||
|
@ -17,11 +17,11 @@ MIN_SDK=21
|
||||
TARGET_SDK=26
|
||||
BUILD_TOOLS=26.0.0
|
||||
|
||||
KAU=bb91aea
|
||||
KAU=3.0
|
||||
KOTLIN=1.1.3-2
|
||||
CRASHLYTICS=2.6.8
|
||||
DBFLOW=4.0.5
|
||||
GLIDE=4.0.0-RC1
|
||||
IAB=1.0.42
|
||||
IICON_COMMUNITY=1.9.32.2
|
||||
IICON_MATERIAL=2.2.0.3
|
||||
JSOUP=1.10.3
|
||||
|
Loading…
Reference in New Issue
Block a user