Fix subscriptions new UI

This commit is contained in:
max-klaus 2021-04-14 19:38:46 +03:00
parent 0c6a7128ea
commit d9baad5644
5 changed files with 309 additions and 103 deletions

View file

@ -18,11 +18,12 @@ import net.osmand.plus.OsmandApplication;
import net.osmand.plus.OsmandPlugin; import net.osmand.plus.OsmandPlugin;
import net.osmand.plus.R; import net.osmand.plus.R;
import net.osmand.plus.inapp.InAppPurchases.InAppPurchase; import net.osmand.plus.inapp.InAppPurchases.InAppPurchase;
import net.osmand.plus.inapp.InAppPurchases.InAppPurchase.PurchaseState;
import net.osmand.plus.inapp.InAppPurchases.InAppSubscription; import net.osmand.plus.inapp.InAppPurchases.InAppSubscription;
import net.osmand.plus.inapp.InAppPurchases.InAppSubscription.SubscriptionState; import net.osmand.plus.inapp.InAppPurchases.InAppSubscription.SubscriptionState;
import net.osmand.plus.inapp.InAppPurchases.PurchaseInfo;
import net.osmand.plus.inapp.InAppPurchasesImpl.InAppPurchaseLiveUpdatesOldSubscription; import net.osmand.plus.inapp.InAppPurchasesImpl.InAppPurchaseLiveUpdatesOldSubscription;
import net.osmand.plus.inapp.util.BillingManager; import net.osmand.plus.inapp.util.BillingManager;
import net.osmand.plus.settings.backend.CommonPreference;
import net.osmand.plus.settings.backend.OsmandSettings; import net.osmand.plus.settings.backend.OsmandSettings;
import net.osmand.plus.srtmplugin.SRTMPlugin; import net.osmand.plus.srtmplugin.SRTMPlugin;
import net.osmand.util.Algorithms; import net.osmand.util.Algorithms;
@ -310,8 +311,8 @@ public class InAppPurchaseHelperImpl extends InAppPurchaseHelper {
} }
} }
} }
for (Entry<String, SubscriptionState> entry : subscriptionStateMap.entrySet()) { for (Entry<String, SubscriptionStateHolder> entry : subscriptionStateMap.entrySet()) {
SubscriptionState state = entry.getValue(); SubscriptionState state = entry.getValue().state;
if (state == SubscriptionState.PAUSED || state == SubscriptionState.ON_HOLD) { if (state == SubscriptionState.PAUSED || state == SubscriptionState.ON_HOLD) {
String sku = entry.getKey(); String sku = entry.getKey();
if (!result.contains(sku)) { if (!result.contains(sku)) {
@ -492,15 +493,17 @@ public class InAppPurchaseHelperImpl extends InAppPurchaseHelper {
} }
private PurchaseInfo getPurchaseInfo(Purchase purchase) { private PurchaseInfo getPurchaseInfo(Purchase purchase) {
return new PurchaseInfo(purchase.getSku(), purchase.getOrderId(), purchase.getPurchaseToken()); return new PurchaseInfo(purchase.getSku(), purchase.getOrderId(), purchase.getPurchaseToken(),
purchase.getPurchaseTime(), purchase.getPurchaseState(), purchase.isAcknowledged(), purchase.isAutoRenewing());
} }
private void fetchInAppPurchase(@NonNull InAppPurchase inAppPurchase, @NonNull SkuDetails skuDetails, @Nullable Purchase purchase) { private void fetchInAppPurchase(@NonNull InAppPurchase inAppPurchase, @NonNull SkuDetails skuDetails, @Nullable Purchase purchase) {
if (purchase != null) { if (purchase != null) {
inAppPurchase.setPurchaseState(InAppPurchase.PurchaseState.PURCHASED); inAppPurchase.setPurchaseState(PurchaseState.PURCHASED);
inAppPurchase.setPurchaseTime(purchase.getPurchaseTime()); inAppPurchase.setPurchaseInfo(ctx, getPurchaseInfo(purchase));
} else { } else {
inAppPurchase.setPurchaseState(InAppPurchase.PurchaseState.NOT_PURCHASED); inAppPurchase.setPurchaseState(PurchaseState.NOT_PURCHASED);
inAppPurchase.restorePurchaseInfo(ctx);
} }
inAppPurchase.setPrice(skuDetails.getPrice()); inAppPurchase.setPrice(skuDetails.getPrice());
inAppPurchase.setPriceCurrencyCode(skuDetails.getPriceCurrencyCode()); inAppPurchase.setPriceCurrencyCode(skuDetails.getPriceCurrencyCode());
@ -519,18 +522,17 @@ public class InAppPurchaseHelperImpl extends InAppPurchaseHelper {
} }
if (inAppPurchase instanceof InAppSubscription) { if (inAppPurchase instanceof InAppSubscription) {
InAppSubscription s = (InAppSubscription) inAppPurchase; InAppSubscription s = (InAppSubscription) inAppPurchase;
s.restoreState(ctx);
SubscriptionState state = subscriptionStateMap.get(inAppPurchase.getSku()); s.restoreExpireTime(ctx);
s.setState(state == null ? SubscriptionState.UNDEFINED : state); SubscriptionStateHolder stateHolder = subscriptionStateMap.get(s.getSku());
CommonPreference<String> statePref = ctx.getSettings().registerStringPreference( if (stateHolder != null) {
s.getSku() + "_state", SubscriptionState.UNDEFINED.getStateStr()).makeGlobal(); s.setState(ctx, stateHolder.state);
s.setPrevState(SubscriptionState.getByStateStr(statePref.get())); s.setExpireTime(ctx, stateHolder.expireTime);
statePref.set(s.getState().getStateStr()); }
if (s.getState().isGone() && s.hasStateChanged()) { if (s.getState().isGone() && s.hasStateChanged()) {
ctx.getSettings().LIVE_UPDATES_EXPIRED_FIRST_DLG_SHOWN_TIME.set(0L); ctx.getSettings().LIVE_UPDATES_EXPIRED_FIRST_DLG_SHOWN_TIME.set(0L);
ctx.getSettings().LIVE_UPDATES_EXPIRED_SECOND_DLG_SHOWN_TIME.set(0L); ctx.getSettings().LIVE_UPDATES_EXPIRED_SECOND_DLG_SHOWN_TIME.set(0L);
} }
String introductoryPrice = skuDetails.getIntroductoryPrice(); String introductoryPrice = skuDetails.getIntroductoryPrice();
String introductoryPricePeriod = skuDetails.getIntroductoryPricePeriod(); String introductoryPricePeriod = skuDetails.getIntroductoryPricePeriod();
int introductoryPriceCycles = skuDetails.getIntroductoryPriceCycles(); int introductoryPriceCycles = skuDetails.getIntroductoryPriceCycles();

View file

@ -29,6 +29,7 @@ import net.osmand.plus.OsmandApplication;
import net.osmand.plus.inapp.InAppPurchases.InAppPurchase; import net.osmand.plus.inapp.InAppPurchases.InAppPurchase;
import net.osmand.plus.inapp.InAppPurchases.InAppSubscription; import net.osmand.plus.inapp.InAppPurchases.InAppSubscription;
import net.osmand.plus.inapp.InAppPurchases.InAppSubscriptionIntroductoryInfo; import net.osmand.plus.inapp.InAppPurchases.InAppSubscriptionIntroductoryInfo;
import net.osmand.plus.inapp.InAppPurchases.PurchaseInfo;
import net.osmand.plus.inapp.InAppPurchasesImpl.InAppPurchaseLiveUpdatesOldSubscription; import net.osmand.plus.inapp.InAppPurchasesImpl.InAppPurchaseLiveUpdatesOldSubscription;
import net.osmand.plus.settings.backend.OsmandSettings; import net.osmand.plus.settings.backend.OsmandSettings;
import net.osmand.util.Algorithms; import net.osmand.util.Algorithms;
@ -48,7 +49,7 @@ public class InAppPurchaseHelperImpl extends InAppPurchaseHelper {
private List<ProductInfo> productInfos; private List<ProductInfo> productInfos;
private OwnedPurchasesResult ownedSubscriptions; private OwnedPurchasesResult ownedSubscriptions;
private List<OwnedPurchasesResult> ownedInApps = new ArrayList<>(); private final List<OwnedPurchasesResult> ownedInApps = new ArrayList<>();
public InAppPurchaseHelperImpl(OsmandApplication ctx) { public InAppPurchaseHelperImpl(OsmandApplication ctx) {
super(ctx); super(ctx);
@ -233,15 +234,18 @@ public class InAppPurchaseHelperImpl extends InAppPurchaseHelper {
} }
private PurchaseInfo getPurchaseInfo(InAppPurchaseData purchase) { private PurchaseInfo getPurchaseInfo(InAppPurchaseData purchase) {
return new PurchaseInfo(purchase.getProductId(), purchase.getSubscriptionId(), purchase.getPurchaseToken()); return new PurchaseInfo(purchase.getProductId(), purchase.getSubscriptionId(), purchase.getPurchaseToken(),
purchase.getPurchaseTime(), purchase.getPurchaseState(), true, purchase.isAutoRenewing());
} }
private void fetchInAppPurchase(@NonNull InAppPurchase inAppPurchase, @NonNull ProductInfo productInfo, @Nullable InAppPurchaseData purchaseData) { private void fetchInAppPurchase(@NonNull InAppPurchase inAppPurchase, @NonNull ProductInfo productInfo, @Nullable InAppPurchaseData purchaseData) {
if (purchaseData != null) { if (purchaseData != null) {
inAppPurchase.setPurchaseState(InAppPurchase.PurchaseState.PURCHASED); inAppPurchase.setPurchaseState(InAppPurchase.PurchaseState.PURCHASED);
inAppPurchase.setPurchaseTime(purchaseData.getPurchaseTime()); inAppPurchase.setPurchaseTime(purchaseData.getPurchaseTime());
inAppPurchase.setPurchaseInfo(ctx, getPurchaseInfo(purchaseData));
} else { } else {
inAppPurchase.setPurchaseState(InAppPurchase.PurchaseState.NOT_PURCHASED); inAppPurchase.setPurchaseState(InAppPurchase.PurchaseState.NOT_PURCHASED);
inAppPurchase.restorePurchaseInfo(ctx);
} }
inAppPurchase.setPrice(productInfo.getPrice()); inAppPurchase.setPrice(productInfo.getPrice());
inAppPurchase.setPriceCurrencyCode(productInfo.getCurrency()); inAppPurchase.setPriceCurrencyCode(productInfo.getCurrency());

View file

@ -21,6 +21,7 @@ import net.osmand.plus.inapp.InAppPurchases.InAppPurchase.PurchaseState;
import net.osmand.plus.inapp.InAppPurchases.InAppSubscription; import net.osmand.plus.inapp.InAppPurchases.InAppSubscription;
import net.osmand.plus.inapp.InAppPurchases.InAppSubscription.SubscriptionState; import net.osmand.plus.inapp.InAppPurchases.InAppSubscription.SubscriptionState;
import net.osmand.plus.inapp.InAppPurchases.InAppSubscriptionList; import net.osmand.plus.inapp.InAppPurchases.InAppSubscriptionList;
import net.osmand.plus.inapp.InAppPurchases.PurchaseInfo;
import net.osmand.plus.liveupdates.CountrySelectionFragment; import net.osmand.plus.liveupdates.CountrySelectionFragment;
import net.osmand.plus.liveupdates.CountrySelectionFragment.CountryItem; import net.osmand.plus.liveupdates.CountrySelectionFragment.CountryItem;
import net.osmand.plus.settings.backend.OsmandSettings; import net.osmand.plus.settings.backend.OsmandSettings;
@ -52,7 +53,7 @@ public abstract class InAppPurchaseHelper {
protected InAppPurchases purchases; protected InAppPurchases purchases;
protected long lastValidationCheckTime; protected long lastValidationCheckTime;
protected boolean inventoryRequested; protected boolean inventoryRequested;
protected Map<String, SubscriptionState> subscriptionStateMap = new HashMap<>(); protected Map<String, SubscriptionStateHolder> subscriptionStateMap = new HashMap<>();
private static final long PURCHASE_VALIDATION_PERIOD_MSEC = 1000 * 60 * 60 * 24; // daily private static final long PURCHASE_VALIDATION_PERIOD_MSEC = 1000 * 60 * 60 * 24; // daily
@ -85,6 +86,11 @@ public abstract class InAppPurchaseHelper {
void onFail(); void onFail();
} }
static class SubscriptionStateHolder {
SubscriptionState state = SubscriptionState.UNDEFINED;
long expireTime = 0;
}
public enum InAppPurchaseTaskType { public enum InAppPurchaseTaskType {
REQUEST_INVENTORY, REQUEST_INVENTORY,
PURCHASE_FULL_VERSION, PURCHASE_FULL_VERSION,
@ -112,30 +118,6 @@ public abstract class InAppPurchaseHelper {
void onCommandDone(@NonNull InAppCommand command); void onCommandDone(@NonNull InAppCommand command);
} }
public static class PurchaseInfo {
private String sku;
private String orderId;
private String purchaseToken;
public PurchaseInfo(String sku, String orderId, String purchaseToken) {
this.sku = sku;
this.orderId = orderId;
this.purchaseToken = purchaseToken;
}
public String getSku() {
return sku;
}
public String getOrderId() {
return orderId;
}
public String getPurchaseToken() {
return purchaseToken;
}
}
public String getToken() { public String getToken() {
return token; return token;
} }
@ -193,6 +175,11 @@ public abstract class InAppPurchaseHelper {
return purchases.getPurchasedMonthlyLiveUpdates(); return purchases.getPurchasedMonthlyLiveUpdates();
} }
@Nullable
public InAppSubscription getAnyPurchasedSubscription() {
return purchases.getAnyPurchasedSubscription();
}
public InAppPurchaseHelper(OsmandApplication ctx) { public InAppPurchaseHelper(OsmandApplication ctx) {
this.ctx = ctx; this.ctx = ctx;
isDeveloperVersion = Version.isDeveloperVersion(ctx); isDeveloperVersion = Version.isDeveloperVersion(ctx);
@ -202,8 +189,7 @@ public abstract class InAppPurchaseHelper {
public List<InAppSubscription> getEverMadeSubscriptions() { public List<InAppSubscription> getEverMadeSubscriptions() {
List<InAppSubscription> subscriptions = new ArrayList<>(); List<InAppSubscription> subscriptions = new ArrayList<>();
for (InAppSubscription subscription : getLiveUpdates().getVisibleSubscriptions()) { for (InAppSubscription subscription : getLiveUpdates().getVisibleSubscriptions()) {
SubscriptionState state = subscription.getState(); if (subscription.isPurchased() || subscription.getState() != SubscriptionState.UNDEFINED) {
if (state != SubscriptionState.UNDEFINED) {
subscriptions.add(subscription); subscriptions.add(subscription);
} }
} }
@ -448,15 +434,22 @@ public abstract class InAppPurchaseHelper {
} }
if (subscriptionsStateJson != null) { if (subscriptionsStateJson != null) {
inventoryRequested = true; inventoryRequested = true;
Map<String, SubscriptionState> subscriptionStateMap = new HashMap<>(); Map<String, SubscriptionStateHolder> subscriptionStateMap = new HashMap<>();
try { try {
JSONArray subArrJson = new JSONArray(subscriptionsStateJson); JSONArray subArrJson = new JSONArray(subscriptionsStateJson);
for (int i = 0; i < subArrJson.length(); i++) { for (int i = 0; i < subArrJson.length(); i++) {
JSONObject subObj = subArrJson.getJSONObject(i); JSONObject subObj = subArrJson.getJSONObject(i);
String sku = subObj.getString("sku"); String sku = subObj.getString("sku");
String state = subObj.getString("state"); String state = subObj.getString("state");
long expireTime = 0;
if (subObj.has("expire_time")) {
expireTime = subObj.getLong("expire_time");
}
if (!Algorithms.isEmpty(sku) && !Algorithms.isEmpty(state)) { if (!Algorithms.isEmpty(sku) && !Algorithms.isEmpty(state)) {
subscriptionStateMap.put(sku, SubscriptionState.getByStateStr(state)); SubscriptionStateHolder stateHolder = new SubscriptionStateHolder();
stateHolder.state = SubscriptionState.getByStateStr(state);
stateHolder.expireTime = expireTime;
subscriptionStateMap.put(sku, stateHolder);
} }
} }
} catch (JSONException e) { } catch (JSONException e) {
@ -500,12 +493,14 @@ public abstract class InAppPurchaseHelper {
protected void onPurchaseDone(PurchaseInfo info) { protected void onPurchaseDone(PurchaseInfo info) {
logDebug("Purchase successful."); logDebug("Purchase successful.");
InAppPurchase liveUpdatesPurchase = getLiveUpdates().getSubscriptionBySku(info.getSku()); InAppSubscription liveUpdatesPurchase = getLiveUpdates().getSubscriptionBySku(info.getSku());
if (liveUpdatesPurchase != null) { if (liveUpdatesPurchase != null) {
// bought live updates // bought live updates
logDebug("Live updates subscription purchased."); logDebug("Live updates subscription purchased.");
final String sku = liveUpdatesPurchase.getSku(); final String sku = liveUpdatesPurchase.getSku();
liveUpdatesPurchase.setPurchaseState(PurchaseState.PURCHASED); liveUpdatesPurchase.setPurchaseState(PurchaseState.PURCHASED);
liveUpdatesPurchase.setPurchaseInfo(ctx, info);
liveUpdatesPurchase.setState(ctx, SubscriptionState.UNDEFINED);
sendTokens(Collections.singletonList(info), new OnRequestResultListener() { sendTokens(Collections.singletonList(info), new OnRequestResultListener() {
@Override @Override
public void onResult(String result) { public void onResult(String result) {
@ -525,6 +520,7 @@ public abstract class InAppPurchaseHelper {
} else if (info.getSku().equals(getFullVersion().getSku())) { } else if (info.getSku().equals(getFullVersion().getSku())) {
// bought full version // bought full version
getFullVersion().setPurchaseState(PurchaseState.PURCHASED); getFullVersion().setPurchaseState(PurchaseState.PURCHASED);
getFullVersion().setPurchaseInfo(ctx, info);
logDebug("Full version purchased."); logDebug("Full version purchased.");
showToast(ctx.getString(R.string.full_version_thanks)); showToast(ctx.getString(R.string.full_version_thanks));
ctx.getSettings().FULL_VERSION_PURCHASED.set(true); ctx.getSettings().FULL_VERSION_PURCHASED.set(true);
@ -536,6 +532,7 @@ public abstract class InAppPurchaseHelper {
} else if (info.getSku().equals(getDepthContours().getSku())) { } else if (info.getSku().equals(getDepthContours().getSku())) {
// bought sea depth contours // bought sea depth contours
getDepthContours().setPurchaseState(PurchaseState.PURCHASED); getDepthContours().setPurchaseState(PurchaseState.PURCHASED);
getDepthContours().setPurchaseInfo(ctx, info);
logDebug("Sea depth contours purchased."); logDebug("Sea depth contours purchased.");
showToast(ctx.getString(R.string.sea_depth_thanks)); showToast(ctx.getString(R.string.sea_depth_thanks));
ctx.getSettings().DEPTH_CONTOURS_PURCHASED.set(true); ctx.getSettings().DEPTH_CONTOURS_PURCHASED.set(true);
@ -548,6 +545,7 @@ public abstract class InAppPurchaseHelper {
} else if (info.getSku().equals(getContourLines().getSku())) { } else if (info.getSku().equals(getContourLines().getSku())) {
// bought contour lines // bought contour lines
getContourLines().setPurchaseState(PurchaseState.PURCHASED); getContourLines().setPurchaseState(PurchaseState.PURCHASED);
getContourLines().setPurchaseInfo(ctx, info);
logDebug("Contours lines purchased."); logDebug("Contours lines purchased.");
showToast(ctx.getString(R.string.contour_lines_thanks)); showToast(ctx.getString(R.string.contour_lines_thanks));
ctx.getSettings().CONTOUR_LINES_PURCHASED.set(true); ctx.getSettings().CONTOUR_LINES_PURCHASED.set(true);

View file

@ -7,33 +7,41 @@ import android.text.Spannable;
import android.text.SpannableStringBuilder; import android.text.SpannableStringBuilder;
import android.text.style.ForegroundColorSpan; import android.text.style.ForegroundColorSpan;
import androidx.annotation.ColorInt;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import net.osmand.AndroidUtils; import net.osmand.AndroidUtils;
import net.osmand.Period; import net.osmand.Period;
import net.osmand.Period.PeriodUnit; import net.osmand.Period.PeriodUnit;
import net.osmand.plus.OsmandApplication; import net.osmand.plus.OsmandApplication;
import net.osmand.plus.R; import net.osmand.plus.R;
import net.osmand.plus.helpers.FontCache; import net.osmand.plus.helpers.FontCache;
import net.osmand.plus.settings.backend.CommonPreference;
import net.osmand.plus.settings.backend.OsmandSettings;
import net.osmand.plus.widgets.style.CustomTypefaceSpan; import net.osmand.plus.widgets.style.CustomTypefaceSpan;
import net.osmand.util.Algorithms; import net.osmand.util.Algorithms;
import org.json.JSONException;
import org.json.JSONObject;
import java.text.NumberFormat; import java.text.NumberFormat;
import java.text.ParseException; import java.text.ParseException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.Currency; import java.util.Currency;
import java.util.Date;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import androidx.annotation.ColorInt;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
public abstract class InAppPurchases { public abstract class InAppPurchases {
protected InAppPurchase fullVersion; protected InAppPurchase fullVersion;
@ -47,6 +55,10 @@ public abstract class InAppPurchases {
protected InAppPurchases(OsmandApplication ctx) { protected InAppPurchases(OsmandApplication ctx) {
} }
private static OsmandSettings getSettings(@NonNull Context ctx) {
return ((OsmandApplication) ctx.getApplicationContext()).getSettings();
}
public InAppPurchase getFullVersion() { public InAppPurchase getFullVersion() {
return fullVersion; return fullVersion;
} }
@ -83,6 +95,17 @@ public abstract class InAppPurchases {
return null; return null;
} }
@Nullable
public InAppSubscription getAnyPurchasedSubscription() {
List<InAppSubscription> allSubscriptions = liveUpdates.getAllSubscriptions();
for (InAppSubscription subscription : allSubscriptions) {
if (subscription.isAnyPurchased()) {
return subscription;
}
}
return null;
}
public InAppSubscriptionList getLiveUpdates() { public InAppSubscriptionList getLiveUpdates() {
return liveUpdates; return liveUpdates;
} }
@ -231,7 +254,7 @@ public abstract class InAppPurchases {
private double priceValue; private double priceValue;
private String priceCurrencyCode; private String priceCurrencyCode;
private PurchaseState purchaseState = PurchaseState.UNKNOWN; private PurchaseState purchaseState = PurchaseState.UNKNOWN;
private long purchaseTime; private PurchaseInfo purchaseInfo;
double monthlyPriceValue; double monthlyPriceValue;
boolean donationSupported = false; boolean donationSupported = false;
@ -253,6 +276,37 @@ public abstract class InAppPurchases {
return sku; return sku;
} }
@Nullable
public String getOrderId() {
return purchaseInfo != null ? purchaseInfo.getOrderId() : null;
}
private CommonPreference<String> getPurchaseInfoPref(@NonNull Context ctx) {
return getSettings(ctx).registerStringPreference(sku + "_purchase_info", "").makeGlobal();
}
public boolean storePurchaseInfo(@NonNull Context ctx) {
PurchaseInfo purchaseInfo = this.purchaseInfo;
if (purchaseInfo != null) {
getPurchaseInfoPref(ctx).set(purchaseInfo.toJson());
return true;
}
return false;
}
public boolean restorePurchaseInfo(@NonNull Context ctx) {
String json = getPurchaseInfoPref(ctx).get();
if (!Algorithms.isEmpty(json)) {
try {
purchaseInfo = new PurchaseInfo(json);
} catch (JSONException e) {
// ignore
}
return true;
}
return false;
}
public String getPrice(Context ctx) { public String getPrice(Context ctx) {
if (!Algorithms.isEmpty(price)) { if (!Algorithms.isEmpty(price)) {
return price; return price;
@ -266,11 +320,16 @@ public abstract class InAppPurchases {
} }
public long getPurchaseTime() { public long getPurchaseTime() {
return purchaseTime; return purchaseInfo != null ? purchaseInfo.getPurchaseTime() : 0;
} }
public void setPurchaseTime(long purchaseTime) { public PurchaseInfo getPurchaseInfo() {
this.purchaseTime = purchaseTime; return purchaseInfo;
}
void setPurchaseInfo(@NonNull Context ctx, PurchaseInfo purchaseInfo) {
this.purchaseInfo = purchaseInfo;
storePurchaseInfo(ctx);
} }
public String getDefaultPrice(Context ctx) { public String getDefaultPrice(Context ctx) {
@ -571,35 +630,33 @@ public abstract class InAppPurchases {
public static abstract class InAppSubscription extends InAppPurchase { public static abstract class InAppSubscription extends InAppPurchase {
private Map<String, InAppSubscription> upgrades = new ConcurrentHashMap<>(); private final Map<String, InAppSubscription> upgrades = new ConcurrentHashMap<>();
private String skuNoVersion; private final String skuNoVersion;
private String subscriptionPeriodString; private String subscriptionPeriodString;
private Period subscriptionPeriod; private Period subscriptionPeriod;
private boolean upgrade = false; private boolean upgrade = false;
private SubscriptionState state = SubscriptionState.UNDEFINED; private SubscriptionState state = SubscriptionState.UNDEFINED;
private SubscriptionState prevState = SubscriptionState.UNDEFINED; private SubscriptionState previousState = SubscriptionState.UNDEFINED;
private long expireTime = 0;
private InAppSubscriptionIntroductoryInfo introductoryInfo; private InAppSubscriptionIntroductoryInfo introductoryInfo;
public enum SubscriptionState { public enum SubscriptionState {
UNDEFINED("undefined", 0, 0), UNDEFINED("undefined", R.string.shared_string_undefined),
ACTIVE("active", R.string.osm_live_active, R.drawable.bg_osmand_live_active), ACTIVE("active", R.string.osm_live_active),
CANCELLED("cancelled", R.string.osmand_live_cancelled, R.drawable.bg_osmand_live_cancelled), CANCELLED("cancelled", R.string.osmand_live_cancelled),
IN_GRACE_PERIOD("in_grace_period", R.string.in_grace_period, R.drawable.bg_osmand_live_active), IN_GRACE_PERIOD("in_grace_period", R.string.in_grace_period),
ON_HOLD("on_hold", R.string.on_hold, R.drawable.bg_osmand_live_cancelled), ON_HOLD("on_hold", R.string.on_hold),
PAUSED("paused", R.string.shared_string_paused, R.drawable.bg_osmand_live_cancelled), PAUSED("paused", R.string.shared_string_paused),
EXPIRED("expired", R.string.expired, R.drawable.bg_osmand_live_cancelled); EXPIRED("expired", R.string.expired);
private final String stateStr; private final String stateStr;
@StringRes @StringRes
private final int stringRes; private final int stringRes;
@DrawableRes
private final int backgroundRes;
SubscriptionState(@NonNull String stateStr, @StringRes int stringRes, @DrawableRes int backgroundRes) { SubscriptionState(@NonNull String stateStr, @StringRes int stringRes) {
this.stateStr = stateStr; this.stateStr = stateStr;
this.stringRes = stringRes; this.stringRes = stringRes;
this.backgroundRes = backgroundRes;
} }
public String getStateStr() { public String getStateStr() {
@ -611,11 +668,6 @@ public abstract class InAppPurchases {
return stringRes; return stringRes;
} }
@DrawableRes
public int getBackgroundRes() {
return backgroundRes;
}
@NonNull @NonNull
public static SubscriptionState getByStateStr(@NonNull String stateStr) { public static SubscriptionState getByStateStr(@NonNull String stateStr) {
for (SubscriptionState state : SubscriptionState.values()) { for (SubscriptionState state : SubscriptionState.values()) {
@ -678,21 +730,76 @@ public abstract class InAppPurchases {
return state; return state;
} }
public void setState(@NonNull SubscriptionState state) { public void setState(@NonNull Context ctx, @NonNull SubscriptionState state) {
this.state = state; this.state = state;
storeState(ctx, state);
} }
@NonNull @NonNull
public SubscriptionState getPrevState() { public SubscriptionState getPreviousState() {
return prevState; return previousState;
}
public void setPrevState(@NonNull SubscriptionState prevState) {
this.prevState = prevState;
} }
public boolean hasStateChanged() { public boolean hasStateChanged() {
return state != prevState; return state != previousState;
}
private CommonPreference<String> getStatePref(@NonNull Context ctx) {
return getSettings(ctx).registerStringPreference(getSku() + "_state", "").makeGlobal();
}
void storeState(@NonNull Context ctx, @NonNull SubscriptionState state) {
getStatePref(ctx).set(state.getStateStr());
}
boolean restoreState(@NonNull Context ctx) {
String stateStr = getStatePref(ctx).get();
if (!Algorithms.isEmpty(stateStr)) {
SubscriptionState state = SubscriptionState.getByStateStr(stateStr);
this.previousState = state;
this.state = state;
return true;
}
return false;
}
public long getCalculatedExpiredTime() {
long purchaseTime = getPurchaseTime();
Period period = getSubscriptionPeriod();
if (purchaseTime == 0 || period == null || period.getUnit() == null) {
return 0;
}
Date date = new Date(purchaseTime);
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
calendar.add(period.getUnit().getCalendarIdx(), period.getNumberOfUnits());
return calendar.getTimeInMillis();
}
public long getExpireTime() {
return expireTime;
}
public void setExpireTime(@NonNull Context ctx, long expireTime) {
this.expireTime = expireTime;
storeExpireTime(ctx, expireTime);
}
private CommonPreference<Long> getExpireTimePref(@NonNull Context ctx) {
return getSettings(ctx).registerLongPreference(getSku() + "_expire_time", 0L).makeGlobal();
}
boolean restoreExpireTime(@NonNull Context ctx) {
Long expireTime = getExpireTimePref(ctx).get();
if (expireTime != null) {
this.expireTime = expireTime;
return true;
}
return false;
}
void storeExpireTime(@NonNull Context ctx, long expireTime) {
getExpireTimePref(ctx).set(expireTime);
} }
public boolean isAnyPurchased() { public boolean isAnyPurchased() {
@ -999,5 +1106,95 @@ public abstract class InAppPurchases {
return null; return null;
} }
} }
public static class PurchaseInfo {
private String sku;
private String orderId;
private String purchaseToken;
private long purchaseTime;
private int purchaseState;
private boolean acknowledged;
private boolean autoRenewing;
PurchaseInfo(String sku, String orderId, String purchaseToken, long purchaseTime,
int purchaseState, boolean acknowledged, boolean autoRenewing) {
this.sku = sku;
this.orderId = orderId;
this.purchaseToken = purchaseToken;
this.purchaseTime = purchaseTime;
this.purchaseState = purchaseState;
this.acknowledged = acknowledged;
this.autoRenewing = autoRenewing;
}
PurchaseInfo(@NonNull String json) throws JSONException {
parseJson(json);
}
public String getSku() {
return sku;
}
public String getOrderId() {
return orderId;
}
public String getPurchaseToken() {
return purchaseToken;
}
public long getPurchaseTime() {
return purchaseTime;
}
public int getPurchaseState() {
return purchaseState;
}
public boolean isAcknowledged() {
return acknowledged;
}
public boolean isAutoRenewing() {
return autoRenewing;
}
public String toJson() {
Map<String, Object> jsonMap = new HashMap<>();
jsonMap.put("sku", sku);
jsonMap.put("orderId", orderId);
jsonMap.put("purchaseToken", purchaseToken);
jsonMap.put("purchaseTime", purchaseTime);
jsonMap.put("purchaseState", purchaseState);
jsonMap.put("acknowledged", acknowledged);
jsonMap.put("autoRenewing", autoRenewing);
return new JSONObject(jsonMap).toString();
}
public void parseJson(@NonNull String json) throws JSONException {
JSONObject jsonObj = new JSONObject(json);
if (jsonObj.has("sku")) {
this.sku = jsonObj.getString("sku");
}
if (jsonObj.has("orderId")) {
this.orderId = jsonObj.getString("orderId");
}
if (jsonObj.has("purchaseToken")) {
this.purchaseToken = jsonObj.getString("purchaseToken");
}
if (jsonObj.has("purchaseTime")) {
this.purchaseTime = jsonObj.getLong("purchaseTime");
}
if (jsonObj.has("purchaseState")) {
this.purchaseState = jsonObj.getInt("purchaseState");
}
if (jsonObj.has("acknowledged")) {
this.acknowledged = jsonObj.getBoolean("acknowledged");
}
if (jsonObj.has("autoRenewing")) {
this.autoRenewing = jsonObj.getBoolean("autoRenewing");
}
}
}
} }

View file

@ -7,7 +7,6 @@ import android.view.ViewGroup;
import android.widget.TextView; import android.widget.TextView;
import net.osmand.AndroidUtils; import net.osmand.AndroidUtils;
import net.osmand.Period;
import net.osmand.plus.R; import net.osmand.plus.R;
import net.osmand.plus.UiUtilities; import net.osmand.plus.UiUtilities;
import net.osmand.plus.activities.MapActivity; import net.osmand.plus.activities.MapActivity;
@ -20,11 +19,10 @@ import net.osmand.plus.routepreparationmenu.cards.BaseCard;
import net.osmand.util.Algorithms; import net.osmand.util.Algorithms;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
public class SubscriptionsListCard extends BaseCard { public class SubscriptionsListCard extends BaseCard {
@ -60,7 +58,13 @@ public class SubscriptionsListCard extends BaseCard {
for (int i = 0; i < subscriptions.size(); i++) { for (int i = 0; i < subscriptions.size(); i++) {
InAppSubscription subscription = subscriptions.get(i); InAppSubscription subscription = subscriptions.get(i);
SubscriptionState state = subscription.getState(); SubscriptionState state = subscription.getState();
boolean autoRenewed = state == SubscriptionState.ACTIVE || state == SubscriptionState.IN_GRACE_PERIOD; boolean autoRenewing = false;
if (subscription.isPurchased() && subscription.getPurchaseInfo() != null) {
autoRenewing = subscription.getPurchaseInfo().isAutoRenewing();
state = SubscriptionState.ACTIVE;
} else if (state != SubscriptionState.UNDEFINED) {
autoRenewing = state == SubscriptionState.ACTIVE || state == SubscriptionState.IN_GRACE_PERIOD;
}
View card = inflater.inflate(R.layout.subscription_layout, null, false); View card = inflater.inflate(R.layout.subscription_layout, null, false);
((ViewGroup) view).addView(card); ((ViewGroup) view).addView(card);
@ -72,11 +76,18 @@ public class SubscriptionsListCard extends BaseCard {
AndroidUiHelper.updateVisibility(subscriptionPeriod, true); AndroidUiHelper.updateVisibility(subscriptionPeriod, true);
} }
if (autoRenewed) { if (autoRenewing) {
TextView nextBillingDate = card.findViewById(R.id.next_billing_date); TextView nextBillingDate = card.findViewById(R.id.next_billing_date);
String date = getHumanDate(subscription.getPurchaseTime(), subscription.getSubscriptionPeriod()); String expiredTimeStr = null;
if (!Algorithms.isEmpty(date)) { long expiredTime = subscription.getExpireTime();
nextBillingDate.setText(app.getString(R.string.next_billing_date, date)); if (expiredTime == 0) {
expiredTime = subscription.getCalculatedExpiredTime();
}
if (expiredTime > 0) {
expiredTimeStr = dateFormat.format(expiredTime);
}
if (!Algorithms.isEmpty(expiredTimeStr)) {
nextBillingDate.setText(app.getString(R.string.next_billing_date, expiredTimeStr));
AndroidUiHelper.updateVisibility(nextBillingDate, true); AndroidUiHelper.updateVisibility(nextBillingDate, true);
} }
} else { } else {
@ -102,7 +113,7 @@ public class SubscriptionsListCard extends BaseCard {
TextView status = card.findViewById(R.id.status); TextView status = card.findViewById(R.id.status);
status.setText(app.getString(state.getStringRes())); status.setText(app.getString(state.getStringRes()));
AndroidUtils.setBackground(status, app.getUIUtilities().getIcon(state.getBackgroundRes())); AndroidUtils.setBackground(status, app.getUIUtilities().getIcon(getBackgroundRes(state)));
int dividerLayout = i + 1 == subscriptions.size() ? R.layout.simple_divider_item : R.layout.divider_half_item; int dividerLayout = i + 1 == subscriptions.size() ? R.layout.simple_divider_item : R.layout.divider_half_item;
View divider = inflater.inflate(dividerLayout, (ViewGroup) view, false); View divider = inflater.inflate(dividerLayout, (ViewGroup) view, false);
@ -110,15 +121,9 @@ public class SubscriptionsListCard extends BaseCard {
} }
} }
private String getHumanDate(long time, Period period) { @DrawableRes
if (period == null || period.getUnit() == null) { private int getBackgroundRes(@NonNull SubscriptionState state) {
return ""; return state == SubscriptionState.ACTIVE || state == SubscriptionState.IN_GRACE_PERIOD
} ? R.drawable.bg_osmand_live_active : R.drawable.bg_osmand_live_cancelled;
Date date = new Date(time);
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
calendar.add(period.getUnit().getCalendarIdx(), period.getNumberOfUnits());
date = calendar.getTime();
return dateFormat.format(date);
} }
} }