mirror of https://github.com/tasks/tasks
Convert Inventory to Kotlin
parent
5d771a2449
commit
f8d6914d37
@ -0,0 +1,70 @@
|
|||||||
|
package org.tasks.billing
|
||||||
|
|
||||||
|
import dagger.hilt.android.testing.HiltAndroidTest
|
||||||
|
import dagger.hilt.android.testing.UninstallModules
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
|
import org.junit.Test
|
||||||
|
import org.tasks.LocalBroadcastManager
|
||||||
|
import org.tasks.injection.InjectingTestCase
|
||||||
|
import org.tasks.injection.ProductionModule
|
||||||
|
import org.tasks.preferences.Preferences
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@UninstallModules(ProductionModule::class)
|
||||||
|
@HiltAndroidTest
|
||||||
|
class InventoryTest : InjectingTestCase() {
|
||||||
|
|
||||||
|
@Inject lateinit var preferences: Preferences
|
||||||
|
@Inject lateinit var localBroadcastManager: LocalBroadcastManager
|
||||||
|
@Inject lateinit var signatureVerifier: SignatureVerifier
|
||||||
|
|
||||||
|
lateinit var inventory: Inventory
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun monthlyIsPro() {
|
||||||
|
withPurchases(monthly01)
|
||||||
|
|
||||||
|
assertTrue(inventory.hasPro)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testMonthlyUpgrade() {
|
||||||
|
withPurchases(monthly01, monthly03)
|
||||||
|
|
||||||
|
assertEquals(3, inventory.subscription?.subscriptionPrice)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testMonthlyOverAnnual() {
|
||||||
|
withPurchases(monthly01, annual03)
|
||||||
|
|
||||||
|
assertTrue(inventory.subscription!!.isMonthly)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun isCancelled() {
|
||||||
|
withPurchases(annual03Cancelled)
|
||||||
|
|
||||||
|
assertTrue(inventory.subscription!!.isCanceled)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun cancelledIsStillPro() {
|
||||||
|
withPurchases(annual03Cancelled)
|
||||||
|
|
||||||
|
assertTrue(inventory.subscription!!.isProSubscription)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun withPurchases(vararg purchases: String) {
|
||||||
|
preferences.setPurchases(purchases.toHashSet())
|
||||||
|
inventory = Inventory(preferences, signatureVerifier, localBroadcastManager)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val annual03 = "{\"mOriginalJson\":\"{\\\"orderId\\\":\\\"GPA.3372-3222-8630-38485\\\",\\\"packageName\\\":\\\"org.tasks\\\",\\\"productId\\\":\\\"annual_03\\\",\\\"purchaseTime\\\":1603917413542,\\\"purchaseState\\\":0,\\\"purchaseToken\\\":\\\"ciogggalpbohflhmjamciehl.AO-J1OyjBpBOnwKCOBSN0Cil7yM65ZYnkn9nGqZO1n3nsHIF_LHv0ZuQqNnThB_JuCt-s9wBij1PyOCoP8axqVILXSiavcyPmg\\\",\\\"autoRenewing\\\":true}\",\"mParsedJson\":{\"nameValuePairs\":{\"orderId\":\"GPA.3372-3222-8630-38485\",\"packageName\":\"org.tasks\",\"productId\":\"annual_03\",\"purchaseTime\":1603917413542,\"purchaseState\":0,\"purchaseToken\":\"ciogggalpbohflhmjamciehl.AO-J1OyjBpBOnwKCOBSN0Cil7yM65ZYnkn9nGqZO1n3nsHIF_LHv0ZuQqNnThB_JuCt-s9wBij1PyOCoP8axqVILXSiavcyPmg\",\"autoRenewing\":true}},\"mSignature\":\"Od2ulMjFethYNdA1rTm7AvNMyfFgefCaZhtBeYuTHlMB/XbEd/m9noRlKWMnShFthnQyw97CfrB86aaB52OSWm9pGkPzaRtOJPyL8BJHP9LEjXHOQIQ2Nx9zRF30+EWgV4O0IyeL/o5eUvTQRNnfyUXFdJQRLiKTblQojO6mTCX2fA6lTAntjJpbTbYGuYZjg782gX5HvmwQN5CJu7ZVZCH9AmsnAqZgb7h+MXhquQjv0L4pDVDp3dyDDwgpCAvSRy3550ZANPfNGsQpPr9Iv9IGoK0/INZRrq63VEEAz2mBGkzJgyQUYVtT6AylvNrqdo0w17hs0MLfsj6dwvSlYw\\u003d\\u003d\"}"
|
||||||
|
private const val monthly01 = "{\"mOriginalJson\":\"{\\\"orderId\\\":\\\"GPA.3369-0544-4429-52590\\\",\\\"packageName\\\":\\\"org.tasks\\\",\\\"productId\\\":\\\"monthly_01\\\",\\\"purchaseTime\\\":1603912474316,\\\"purchaseState\\\":0,\\\"purchaseToken\\\":\\\"iibbhlkglfjcgdebphiklajb.AO-J1OyJd2kCytLMfT8Vszibf_E99ffLha5cHgOM8o3gYPKy1kD8nIZh0hcEEyOPe7fsdFJrR1-gtvg8WKLFNJoCdqrerJ2Z6Q\\\",\\\"autoRenewing\\\":true}\",\"mParsedJson\":{\"nameValuePairs\":{\"orderId\":\"GPA.3369-0544-4429-52590\",\"packageName\":\"org.tasks\",\"productId\":\"monthly_01\",\"purchaseTime\":1603912474316,\"purchaseState\":0,\"purchaseToken\":\"iibbhlkglfjcgdebphiklajb.AO-J1OyJd2kCytLMfT8Vszibf_E99ffLha5cHgOM8o3gYPKy1kD8nIZh0hcEEyOPe7fsdFJrR1-gtvg8WKLFNJoCdqrerJ2Z6Q\",\"autoRenewing\":true}},\"mSignature\":\"UK7fdCY61QownZW8jDLB1myUKf1llFh9rj5I7P8V03AgdA6LGpEUiCvMvCqHfMGpY3VewmawezqiCUdGWGr+UgS+6QHEuFjpO8L+E36JUDqlU9uoGrTsXLI1gXQNQElGJ71DrKlFBbyyBHSeGWnzijcq4DyyHQzpmsqijxfs0KGjkta2TiOCtyxS+YA569xaGi6lcLGTyMEe7wS5bcjdfwFir0uVtCP+iqjoEd3kt4/03l9BEJYgf8eBxI0vrm4O+jYDJu8gGMTSQZiSqb0wN4sq8D9ksV+BcI4az6LVa1d6nuD+ob0Woe0/P2uoXG8nTEZJnrAZjkG6q8736HP6rw\\u003d\\u003d\"}"
|
||||||
|
private const val monthly03 = "{\"mOriginalJson\":\"{\\\"orderId\\\":\\\"GPA.3348-6247-8527-38213\\\",\\\"packageName\\\":\\\"org.tasks\\\",\\\"productId\\\":\\\"monthly_03\\\",\\\"purchaseTime\\\":1603912730414,\\\"purchaseState\\\":0,\\\"purchaseToken\\\":\\\"cmomnojdllomadpoinoabbkd.AO-J1OypdY4iXbMrF21L6Evn3wZSccwiBq-d55G1BVcrkwuH69zOuqb35yZnVynEb9KEvnQvgYQsUpv1AD5749iU-eDo4TRV5A\\\",\\\"autoRenewing\\\":true}\",\"mParsedJson\":{\"nameValuePairs\":{\"orderId\":\"GPA.3348-6247-8527-38213\",\"packageName\":\"org.tasks\",\"productId\":\"monthly_03\",\"purchaseTime\":1603912730414,\"purchaseState\":0,\"purchaseToken\":\"cmomnojdllomadpoinoabbkd.AO-J1OypdY4iXbMrF21L6Evn3wZSccwiBq-d55G1BVcrkwuH69zOuqb35yZnVynEb9KEvnQvgYQsUpv1AD5749iU-eDo4TRV5A\",\"autoRenewing\":true}},\"mSignature\":\"FkkW5FPw2elWnenIoQT7U5BnL2prcuK0GJEaHKtObPujSGRfJWFfThe3yuQ0w9AuTO0EDbm7LbJI44AiVJmpva3Iz3U2np2eNBuUAJIw9eECvQjEvuYk6Vq7LIgJwEsTyA8xRwjLJm+R1mmMWOxURmvDVBgDTHCOJsdUI9s52CSTQf2Ek+XABHugrMJudO43LzDuV2sP9mCqXUnSLbBXe3zZKyhhuz7gD+/5yavkRsPOVcZnsJetdxEmnrip8JEvgtHAvciPkvSD/fYeXdAlY2HiQWK/S0/I+yRaCEK8V+Um78ibbYc4Ng5NcXDm44nTv3F6jQEzYy4qRv/ohmwEQg\\u003d\\u003d\"}"
|
||||||
|
private const val annual03Cancelled = "{\"mOriginalJson\":\"{\\\"orderId\\\":\\\"GPA.3372-3222-8630-38485\\\",\\\"packageName\\\":\\\"org.tasks\\\",\\\"productId\\\":\\\"annual_03\\\",\\\"purchaseTime\\\":1603917413542,\\\"purchaseState\\\":0,\\\"purchaseToken\\\":\\\"ciogggalpbohflhmjamciehl.AO-J1OyjBpBOnwKCOBSN0Cil7yM65ZYnkn9nGqZO1n3nsHIF_LHv0ZuQqNnThB_JuCt-s9wBij1PyOCoP8axqVILXSiavcyPmg\\\",\\\"autoRenewing\\\":false}\",\"mParsedJson\":{\"nameValuePairs\":{\"orderId\":\"GPA.3372-3222-8630-38485\",\"packageName\":\"org.tasks\",\"productId\":\"annual_03\",\"purchaseTime\":1603917413542,\"purchaseState\":0,\"purchaseToken\":\"ciogggalpbohflhmjamciehl.AO-J1OyjBpBOnwKCOBSN0Cil7yM65ZYnkn9nGqZO1n3nsHIF_LHv0ZuQqNnThB_JuCt-s9wBij1PyOCoP8axqVILXSiavcyPmg\",\"autoRenewing\":false}},\"mSignature\":\"jL+2qRv0LtCutoJ86NWaInbx/9/kIWbxXRKYkou74TBjwu9KZ89EpJY632ImEy2xfLd8DHuVuWOcZY646I29Ny2E4HYNAsQEg2du4NRXEHZvu+py4Mi212KF8S2EPNdZCor1wiOJ0zRVBiRAtiCfqxHjQdfKn7FpDiHFrUhMu1huEAxJ0Xrnvxcmkouizw3wzKnAvI+O75LIWWZHCy+1o7s285cSKtQoztVY/nHInJLxV6dk93lAivOlEox+VCLU978lUvv45Rue50fMzS2CRsVFmRt9/yTP8RCiQKzGC/pyHtqNj/ceCrDi4VV8JPhsPd4NUaKk82Oq1xmGXtzEcQ\\u003d\\u003d\"}"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,90 +0,0 @@
|
|||||||
package org.tasks.billing;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
import org.tasks.BuildConfig;
|
|
||||||
import org.tasks.LocalBroadcastManager;
|
|
||||||
import org.tasks.R;
|
|
||||||
import org.tasks.preferences.Preferences;
|
|
||||||
import timber.log.Timber;
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
public class Inventory {
|
|
||||||
|
|
||||||
private static final String SKU_VIP = "vip";
|
|
||||||
public static final String SKU_TASKER = "tasker";
|
|
||||||
public static final String SKU_THEMES = "themes";
|
|
||||||
|
|
||||||
private final Preferences preferences;
|
|
||||||
private final SignatureVerifier signatureVerifier;
|
|
||||||
private final LocalBroadcastManager localBroadcastManager;
|
|
||||||
|
|
||||||
private final Map<String, Purchase> purchases = new HashMap<>();
|
|
||||||
private Purchase subscription = null;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public Inventory(
|
|
||||||
Preferences preferences,
|
|
||||||
SignatureVerifier signatureVerifier,
|
|
||||||
LocalBroadcastManager localBroadcastManager) {
|
|
||||||
this.preferences = preferences;
|
|
||||||
this.signatureVerifier = signatureVerifier;
|
|
||||||
this.localBroadcastManager = localBroadcastManager;
|
|
||||||
for (Purchase purchase : preferences.getPurchases()) {
|
|
||||||
verifyAndAdd(purchase);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void clear() {
|
|
||||||
Timber.d("clear()");
|
|
||||||
purchases.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void add(Iterable<Purchase> purchases) {
|
|
||||||
for (Purchase purchase : purchases) {
|
|
||||||
verifyAndAdd(purchase);
|
|
||||||
}
|
|
||||||
preferences.setPurchases(this.purchases.values());
|
|
||||||
localBroadcastManager.broadcastPurchasesUpdated();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void verifyAndAdd(Purchase purchase) {
|
|
||||||
if (signatureVerifier.verifySignature(purchase)) {
|
|
||||||
Timber.d("add(%s)", purchase);
|
|
||||||
purchases.put(purchase.getSku(), purchase);
|
|
||||||
if (purchase.isProSubscription() && (subscription == null || subscription.isCanceled())) {
|
|
||||||
subscription = purchase;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean purchasedTasker() {
|
|
||||||
return hasPro() || purchases.containsKey(SKU_TASKER);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean purchasedThemes() {
|
|
||||||
return hasPro() || purchases.containsKey(SKU_THEMES);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Purchase getSubscription() {
|
|
||||||
return subscription;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasPro() {
|
|
||||||
//noinspection ConstantConditions
|
|
||||||
return subscription != null
|
|
||||||
|| purchases.containsKey(SKU_VIP)
|
|
||||||
|| BuildConfig.FLAVOR.equals("generic")
|
|
||||||
|| (BuildConfig.DEBUG && preferences.getBoolean(R.string.p_debug_pro, false));
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean purchased(String sku) {
|
|
||||||
return purchases.containsKey(sku);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Purchase getPurchase(String sku) {
|
|
||||||
return purchases.get(sku);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,79 @@
|
|||||||
|
package org.tasks.billing
|
||||||
|
|
||||||
|
import org.tasks.BuildConfig
|
||||||
|
import org.tasks.LocalBroadcastManager
|
||||||
|
import org.tasks.R
|
||||||
|
import org.tasks.preferences.Preferences
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.util.*
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
class Inventory @Inject constructor(
|
||||||
|
private val preferences: Preferences,
|
||||||
|
private val signatureVerifier: SignatureVerifier,
|
||||||
|
private val localBroadcastManager: LocalBroadcastManager
|
||||||
|
) {
|
||||||
|
private val purchases: MutableMap<String, Purchase> = HashMap()
|
||||||
|
|
||||||
|
fun clear() {
|
||||||
|
Timber.d("clear()")
|
||||||
|
purchases.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun add(purchases: Iterable<Purchase>) {
|
||||||
|
verifyAndAdd(purchases)
|
||||||
|
preferences.setPurchases(this.purchases.values)
|
||||||
|
localBroadcastManager.broadcastPurchasesUpdated()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun verifyAndAdd(items: Iterable<Purchase>) {
|
||||||
|
for (purchase in items) {
|
||||||
|
if (signatureVerifier.verifySignature(purchase)) {
|
||||||
|
Timber.d("add(%s)", purchase)
|
||||||
|
this.purchases[purchase.sku] = purchase
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hasPro = purchases.values.any { it.isProSubscription } || purchases.containsKey(SKU_VIP)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun purchasedTasker() = hasPro || purchases.containsKey(SKU_TASKER)
|
||||||
|
|
||||||
|
fun purchasedThemes() = hasPro || purchases.containsKey(SKU_THEMES)
|
||||||
|
|
||||||
|
var hasPro = false
|
||||||
|
get() {
|
||||||
|
return BuildConfig.FLAVOR == "generic"
|
||||||
|
|| (BuildConfig.DEBUG && preferences.getBoolean(R.string.p_debug_pro, false))
|
||||||
|
|| field
|
||||||
|
}
|
||||||
|
private set
|
||||||
|
|
||||||
|
fun purchased(sku: String) = purchases.containsKey(sku)
|
||||||
|
|
||||||
|
fun getPurchase(sku: String) = purchases[sku]
|
||||||
|
|
||||||
|
val subscription: Purchase?
|
||||||
|
get() = purchases
|
||||||
|
.values
|
||||||
|
.filter { it.isProSubscription }
|
||||||
|
.sortedWith{ l, r ->
|
||||||
|
r.isMonthly.compareTo(l.isMonthly)
|
||||||
|
.takeIf { it != 0 }?.let { return@sortedWith it }
|
||||||
|
l.isCanceled.compareTo(r.isCanceled)
|
||||||
|
.takeIf { it != 0 }?.let { return@sortedWith it }
|
||||||
|
return@sortedWith r.subscriptionPrice!!.compareTo(l.subscriptionPrice!!)
|
||||||
|
}
|
||||||
|
.firstOrNull()
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val SKU_VIP = "vip"
|
||||||
|
const val SKU_TASKER = "tasker"
|
||||||
|
const val SKU_THEMES = "themes"
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
verifyAndAdd(preferences.purchases)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue