diff --git a/OsmAnd-java/src/main/java/net/osmand/Period.java b/OsmAnd-java/src/main/java/net/osmand/Period.java
new file mode 100644
index 0000000000..d6bd911257
--- /dev/null
+++ b/OsmAnd-java/src/main/java/net/osmand/Period.java
@@ -0,0 +1,171 @@
+package net.osmand;
+
+import java.text.ParseException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class Period {
+
+ public enum PeriodUnit {
+ YEAR("Y"),
+ MONTH("M"),
+ WEEK("W"),
+ DAY("D");
+
+ private String unitStr;
+
+ PeriodUnit(String unitStr) {
+ this.unitStr = unitStr;
+ }
+
+ public String getUnitStr() {
+ return unitStr;
+ }
+
+ public double getMonthsValue() {
+ switch (this) {
+ case YEAR:
+ return 12d;
+ case MONTH:
+ return 1d;
+ case WEEK:
+ return 1/4d;
+ case DAY:
+ return 1/30d;
+ }
+ return 0d;
+ }
+
+ public static PeriodUnit parseUnit(String unitStr) {
+ for (PeriodUnit unit : values()) {
+ if (unit.unitStr.equals(unitStr)) {
+ return unit;
+ }
+ }
+ return null;
+ }
+ }
+
+ private static final Pattern PATTERN =
+ Pattern.compile("^P(?:([-+]?[0-9]+)([YMWD]))?$", Pattern.CASE_INSENSITIVE);
+
+ private PeriodUnit unit;
+
+ private final int numberOfUnits;
+
+ public static Period ofYears(int years) {
+ return new Period(PeriodUnit.YEAR, years);
+ }
+
+ public static Period ofMonths(int months) {
+ return new Period(PeriodUnit.MONTH, months);
+ }
+
+ public static Period ofWeeks(int weeks) {
+ return new Period(PeriodUnit.WEEK, weeks);
+ }
+
+ public static Period ofDays(int days) {
+ return new Period(PeriodUnit.DAY, days);
+ }
+
+ public PeriodUnit getUnit() {
+ return unit;
+ }
+
+ public int getNumberOfUnits() {
+ return numberOfUnits;
+ }
+
+ /**
+ * Obtains a {@code Period} from a text string such as {@code PnY PnM PnD PnW}.
+ *
+ * This will parse the string produced by {@code toString()} which is
+ * based on the ISO-8601 period formats {@code PnY PnM PnD PnW}.
+ *
+ * The string cannot start with negative sign.
+ * The ASCII letter "P" is next in upper or lower case.
+ *
+ * For example, the following are valid inputs:
+ *
+ * "P2Y" -- Period.ofYears(2)
+ * "P3M" -- Period.ofMonths(3)
+ * "P4W" -- Period.ofWeeks(4)
+ * "P5D" -- Period.ofDays(5)
+ *
+ *
+ * @param text the text to parse, not null
+ * @return the parsed period, not null
+ * @throws ParseException if the text cannot be parsed to a period
+ */
+ public static Period parse(CharSequence text) throws ParseException {
+ Matcher matcher = PATTERN.matcher(text);
+ if (matcher.matches()) {
+ String numberOfUnitsMatch = matcher.group(1);
+ String unitMatch = matcher.group(2);
+ if (numberOfUnitsMatch != null && unitMatch != null) {
+ try {
+ int numberOfUnits = parseNumber(numberOfUnitsMatch);
+ PeriodUnit unit = PeriodUnit.parseUnit(unitMatch);
+ return new Period(unit, numberOfUnits);
+ } catch (IllegalArgumentException ex) {
+ throw new ParseException("Text cannot be parsed to a Period: " + text, 0);
+ }
+ }
+ }
+ throw new ParseException("Text cannot be parsed to a Period: " + text, 0);
+ }
+
+ private static int parseNumber(String str) throws ParseException {
+ if (str == null) {
+ return 0;
+ }
+ return Integer.parseInt(str);
+ }
+
+ public Period(PeriodUnit unit, int numberOfUnits) {
+ if (unit == null) {
+ throw new IllegalArgumentException("PeriodUnit cannot be null");
+ }
+ this.unit = unit;
+ this.numberOfUnits = numberOfUnits;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof Period) {
+ Period other = (Period) obj;
+ return unit.ordinal() == other.unit.ordinal() && numberOfUnits == other.numberOfUnits;
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return unit.ordinal() + Integer.rotateLeft(numberOfUnits, 8);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder buf = new StringBuilder();
+ buf.append('P').append(numberOfUnits);
+ switch (unit) {
+ case YEAR:
+ buf.append('Y');
+ break;
+ case MONTH:
+ buf.append('M');
+ break;
+ case WEEK:
+ buf.append('W');
+ break;
+ case DAY:
+ buf.append('D');
+ break;
+ }
+ return buf.toString();
+ }
+}
diff --git a/OsmAnd/res/layout/purchase_dialog_card_button_active_ex.xml b/OsmAnd/res/layout/purchase_dialog_card_button_active_ex.xml
index 5647137c1f..98db3af15f 100644
--- a/OsmAnd/res/layout/purchase_dialog_card_button_active_ex.xml
+++ b/OsmAnd/res/layout/purchase_dialog_card_button_active_ex.xml
@@ -6,52 +6,51 @@
android:layout_height="wrap_content"
android:layout_marginLeft="1dp"
android:layout_marginRight="1dp"
- android:background="?attr/subscription_active_bg_color"
android:orientation="vertical">
-
-
-
-
+ android:orientation="horizontal">
+ android:layout_weight="1"
+ android:orientation="vertical">
+
+
+
+
+
+
@@ -76,7 +84,7 @@
@@ -100,7 +108,7 @@
@@ -108,7 +116,8 @@
@@ -121,7 +130,7 @@
android:letterSpacing="@dimen/text_button_letter_spacing"
android:maxWidth="@dimen/dialog_button_ex_max_width"
android:minWidth="@dimen/dialog_button_ex_min_width"
- android:text="@string/shared_string_cancel"
+ android:text="@string/cancel_subscription"
android:textColor="@color/color_white"
android:textSize="@dimen/text_button_text_size"
osmand:typeface="@string/font_roboto_medium" />
@@ -136,16 +145,8 @@
android:id="@+id/div"
android:layout_width="match_parent"
android:layout_height="1dp"
- android:layout_marginLeft="@dimen/card_padding"
- android:layout_marginRight="@dimen/card_padding"
- android:background="?attr/subscription_active_div_color"
- android:visibility="gone" />
-
-
diff --git a/OsmAnd/res/layout/purchase_dialog_card_button_ex.xml b/OsmAnd/res/layout/purchase_dialog_card_button_ex.xml
index 2dba96adf7..05465c67db 100644
--- a/OsmAnd/res/layout/purchase_dialog_card_button_ex.xml
+++ b/OsmAnd/res/layout/purchase_dialog_card_button_ex.xml
@@ -12,82 +12,52 @@
android:id="@+id/button_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:background="?attr/selectableItemBackground"
android:baselineAligned="false"
- android:minHeight="@dimen/dialog_button_ex_height"
- android:orientation="horizontal"
+ android:orientation="vertical"
android:paddingLeft="@dimen/card_padding"
android:paddingRight="@dimen/card_padding">
-
+ android:text="@string/osm_live_payment_contribute_descr"
+ android:textColor="?attr/card_description_text_color"
+ android:textSize="@dimen/default_desc_text_size"
+ android:visibility="gone"
+ osmand:typeface="@string/font_roboto_regular"
+ tools:visibility="visible" />
-
-
-
-
-
-
-
+
@@ -95,7 +65,7 @@
+ tools:text="7,99€ / year" />
@@ -127,7 +97,7 @@
@@ -135,7 +105,7 @@
+ tools:text="$3.99 for six month\nthan $7.49 / year" />
@@ -164,8 +134,7 @@
android:id="@+id/div"
android:layout_width="match_parent"
android:layout_height="1dp"
- android:layout_marginLeft="@dimen/card_padding"
- android:layout_marginRight="@dimen/card_padding"
+ android:layout_marginTop="@dimen/content_padding_small"
android:background="?attr/wikivoyage_card_divider_color"
android:visibility="gone" />
diff --git a/OsmAnd/res/layout/purchase_dialog_osm_live_card.xml b/OsmAnd/res/layout/purchase_dialog_osm_live_card.xml
index 5697540951..03612b69cc 100644
--- a/OsmAnd/res/layout/purchase_dialog_osm_live_card.xml
+++ b/OsmAnd/res/layout/purchase_dialog_osm_live_card.xml
@@ -64,6 +64,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="2dp"
+ android:paddingBottom="@dimen/content_padding_small"
android:orientation="vertical"
android:visibility="gone" />
diff --git a/OsmAnd/res/values-ru/strings.xml b/OsmAnd/res/values-ru/strings.xml
index 4868711e7d..a795691cac 100644
--- a/OsmAnd/res/values-ru/strings.xml
+++ b/OsmAnd/res/values-ru/strings.xml
@@ -1,5 +1,26 @@
+ День
+ Дня
+ Дней
+ Неделя
+ Недели
+ Недель
+ Месяц
+ Месяца
+ Месяцев
+ Год
+ Года
+ Лет
+ Три месяца
+ Бесплатно
+ Получите %1$d %2$s со скидкой %3$s.
+ Начать бесплатный период в %1$d %2$s.
+ %1$s за первый %2$s
+ %1$s за первые %2$s
+ затем %1$s
+ Отменить подписку
+ %1$s • Экономия %2$s
Автозимник
Ледовый автозимник
Зимники
diff --git a/OsmAnd/res/values/strings.xml b/OsmAnd/res/values/strings.xml
index d00e230512..57674c0e0a 100644
--- a/OsmAnd/res/values/strings.xml
+++ b/OsmAnd/res/values/strings.xml
@@ -11,6 +11,27 @@
Thx - Hardy
-->
+ Day
+ Days
+ Days
+ Week
+ Weeks
+ Weeks
+ Month
+ Months
+ Months
+ Year
+ Years
+ Years
+ Three months
+ Free
+ Get %1$d %2$s at %3$s off.
+ Start your %1$d %2$s free trial.
+ %1$s for first %2$s
+ %1$s for first %2$s
+ then %1$s
+ Cancel subscription
+ %1$s • Save %2$s
Wagon
Pickup truck
Default
diff --git a/OsmAnd/src/net/osmand/AndroidUtils.java b/OsmAnd/src/net/osmand/AndroidUtils.java
index 4957d02d21..a7e923fde5 100644
--- a/OsmAnd/src/net/osmand/AndroidUtils.java
+++ b/OsmAnd/src/net/osmand/AndroidUtils.java
@@ -30,6 +30,8 @@ import android.support.annotation.NonNull;
import android.support.design.widget.Snackbar;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.FileProvider;
+import android.support.v4.text.TextUtilsCompat;
+import android.support.v4.view.ViewCompat;
import android.text.ParcelableSpan;
import android.text.Spannable;
import android.text.SpannableString;
@@ -62,6 +64,7 @@ import java.util.Date;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import static android.content.Context.POWER_SERVICE;
@@ -598,4 +601,8 @@ public class AndroidUtils {
return baseString;
}
}
+
+ public static boolean isRTL() {
+ return TextUtilsCompat.getLayoutDirectionFromLocale(Locale.getDefault()) == ViewCompat.LAYOUT_DIRECTION_RTL;
+ }
}
diff --git a/OsmAnd/src/net/osmand/plus/chooseplan/ChoosePlanDialogFragment.java b/OsmAnd/src/net/osmand/plus/chooseplan/ChoosePlanDialogFragment.java
index 620df4a241..144748dcd8 100644
--- a/OsmAnd/src/net/osmand/plus/chooseplan/ChoosePlanDialogFragment.java
+++ b/OsmAnd/src/net/osmand/plus/chooseplan/ChoosePlanDialogFragment.java
@@ -45,6 +45,7 @@ import net.osmand.plus.inapp.InAppPurchaseHelper.InAppPurchaseListener;
import net.osmand.plus.inapp.InAppPurchaseHelper.InAppPurchaseTaskType;
import net.osmand.plus.inapp.InAppPurchases.InAppPurchase;
import net.osmand.plus.inapp.InAppPurchases.InAppSubscription;
+import net.osmand.plus.inapp.InAppPurchases.InAppSubscriptionIntroductoryInfo;
import net.osmand.plus.liveupdates.SubscriptionFragment;
import net.osmand.plus.srtmplugin.SRTMPlugin;
import net.osmand.plus.widgets.TextViewEx;
@@ -341,38 +342,40 @@ public abstract class ChoosePlanDialogFragment extends BaseOsmAndDialogFragment
} else if (osmLiveCardButtonsContainer != null) {
osmLiveCardButtonsContainer.removeAllViews();
View lastBtn = null;
- InAppSubscription monthlyLiveUpdates = purchaseHelper.getMonthlyLiveUpdates();
- double regularMonthlyPrice = monthlyLiveUpdates.getPriceValue();
List visibleSubscriptions = purchaseHelper.getLiveUpdates().getVisibleSubscriptions();
- boolean anyPurchased = false;
+ boolean anyPurchasedOrIntroducted = false;
for (final InAppSubscription s : visibleSubscriptions) {
- if (s.isPurchased()) {
- anyPurchased = true;
+ if (s.isPurchased() || s.getIntroductoryInfo() != null) {
+ anyPurchasedOrIntroducted = true;
break;
}
}
for (final InAppSubscription s : visibleSubscriptions) {
+ InAppSubscriptionIntroductoryInfo introductoryInfo = s.getIntroductoryInfo();
+ boolean hasIntroductoryInfo = introductoryInfo != null;
+ CharSequence priceTitle = hasIntroductoryInfo ? introductoryInfo.getFormattedDescription(ctx) : s.getPrice(ctx);
+ CharSequence descriptionText = hasIntroductoryInfo ?
+ introductoryInfo.getDescriptionTitle(ctx) : s.getDescription(ctx, purchaseHelper.getMonthlyLiveUpdates());
if (s.isPurchased()) {
View buttonPurchased = inflate(R.layout.purchase_dialog_card_button_active_ex, osmLiveCardButtonsContainer);
- View buttonContainer = buttonPurchased.findViewById(R.id.button_container);
TextViewEx title = (TextViewEx) buttonPurchased.findViewById(R.id.title);
TextViewEx description = (TextViewEx) buttonPurchased.findViewById(R.id.description);
+ TextViewEx descriptionContribute = (TextViewEx) buttonPurchased.findViewById(R.id.description_contribute);
+ descriptionContribute.setVisibility(s.isDonationSupported() ? View.VISIBLE : View.GONE);
TextViewEx buttonTitle = (TextViewEx) buttonPurchased.findViewById(R.id.button_title);
View buttonView = buttonPurchased.findViewById(R.id.button_view);
View buttonCancelView = buttonPurchased.findViewById(R.id.button_cancel_view);
- View divTop = buttonPurchased.findViewById(R.id.div_top);
- View divBottom = buttonPurchased.findViewById(R.id.div_bottom);
View div = buttonPurchased.findViewById(R.id.div);
+ AppCompatImageView rightImage = buttonPurchased.findViewById(R.id.right_image);
title.setText(s.getTitle(ctx));
- description.setText(s.getDescription(ctx));
- buttonTitle.setText(s.getPrice(ctx));
+ description.setText(descriptionText);
+ buttonTitle.setText(priceTitle);
buttonView.setVisibility(View.VISIBLE);
buttonCancelView.setVisibility(View.GONE);
buttonPurchased.setOnClickListener(null);
- divTop.setVisibility(View.VISIBLE);
- div.setVisibility(View.VISIBLE);
- divBottom.setVisibility(View.GONE);
+ div.setVisibility(View.GONE);
+ rightImage.setVisibility(View.GONE);
if (s.isDonationSupported()) {
buttonPurchased.setOnClickListener(new OnClickListener() {
@Override
@@ -387,14 +390,12 @@ public abstract class ChoosePlanDialogFragment extends BaseOsmAndDialogFragment
osmLiveCardButtonsContainer.addView(buttonPurchased);
View buttonCancel = inflate(R.layout.purchase_dialog_card_button_active_ex, osmLiveCardButtonsContainer);
- buttonContainer = buttonCancel.findViewById(R.id.button_container);
title = (TextViewEx) buttonCancel.findViewById(R.id.title);
description = (TextViewEx) buttonCancel.findViewById(R.id.description);
buttonView = buttonCancel.findViewById(R.id.button_view);
buttonCancelView = buttonCancel.findViewById(R.id.button_cancel_view);
- divTop = buttonCancel.findViewById(R.id.div_top);
- divBottom = buttonCancel.findViewById(R.id.div_bottom);
div = buttonCancel.findViewById(R.id.div);
+ rightImage = buttonPurchased.findViewById(R.id.right_image);
title.setText(getString(R.string.osm_live_payment_current_subscription));
description.setText(s.getRenewDescription(ctx));
@@ -406,70 +407,34 @@ public abstract class ChoosePlanDialogFragment extends BaseOsmAndDialogFragment
manageSubscription(ctx, s.getSku());
}
});
- divTop.setVisibility(View.GONE);
- div.setVisibility(View.GONE);
- divBottom.setVisibility(View.VISIBLE);
+ div.setVisibility(View.VISIBLE);
+ rightImage.setVisibility(View.VISIBLE);
osmLiveCardButtonsContainer.addView(buttonCancel);
-
- if (lastBtn != null) {
- View lastBtnDiv = lastBtn.findViewById(R.id.div);
- if (lastBtnDiv != null) {
- lastBtnDiv.setVisibility(View.GONE);
- }
- View lastBtnDivBottom = lastBtn.findViewById(R.id.div_bottom);
- if (lastBtnDivBottom != null) {
- lastBtnDivBottom.setVisibility(View.GONE);
- }
- }
lastBtn = buttonCancel;
} else {
View button = inflate(R.layout.purchase_dialog_card_button_ex, osmLiveCardButtonsContainer);
TextViewEx title = (TextViewEx) button.findViewById(R.id.title);
TextViewEx description = (TextViewEx) button.findViewById(R.id.description);
+ TextViewEx descriptionContribute = (TextViewEx) button.findViewById(R.id.description_contribute);
+ descriptionContribute.setVisibility(s.isDonationSupported() ? View.VISIBLE : View.GONE);
View buttonView = button.findViewById(R.id.button_view);
View buttonExView = button.findViewById(R.id.button_ex_view);
TextViewEx buttonTitle = (TextViewEx) button.findViewById(R.id.button_title);
TextViewEx buttonExTitle = (TextViewEx) button.findViewById(R.id.button_ex_title);
- buttonView.setVisibility(anyPurchased ? View.VISIBLE : View.GONE);
- buttonExView.setVisibility(!anyPurchased ? View.VISIBLE : View.GONE);
+ boolean showSolidButton = !anyPurchasedOrIntroducted || hasIntroductoryInfo;
+ buttonView.setVisibility(!showSolidButton ? View.VISIBLE : View.GONE);
+ buttonExView.setVisibility(showSolidButton ? View.VISIBLE : View.GONE);
- TextViewEx discountRegular = (TextViewEx) button.findViewById(R.id.discount_banner_regular);
- TextViewEx discountActive = (TextViewEx) button.findViewById(R.id.discount_banner_active);
View div = button.findViewById(R.id.div);
title.setText(s.getTitle(ctx));
- description.setText(s.getDescription(ctx));
- buttonTitle.setText(s.getPrice(ctx));
- buttonExTitle.setText(s.getPrice(ctx));
+ description.setText(descriptionText);
+ buttonTitle.setText(priceTitle);
+ buttonExTitle.setText(priceTitle);
- if (regularMonthlyPrice > 0 && s.getMonthlyPriceValue() > 0 && s.getMonthlyPriceValue() < regularMonthlyPrice) {
- int discount = (int) ((1 - s.getMonthlyPriceValue() / regularMonthlyPrice) * 100d);
- String discountStr = discount + "%";
- if (discount > 50) {
- discountActive.setText(String.format(" %s ", getString(R.string.osm_live_payment_discount_descr, discountStr)));
- discountActive.setVisibility(View.VISIBLE);
- discountRegular.setVisibility(View.GONE);
- } else if (discount > 0) {
- discountActive.setVisibility(View.GONE);
- discountRegular.setText(String.format(" %s ", getString(R.string.osm_live_payment_discount_descr, discountStr)));
- discountRegular.setVisibility(View.VISIBLE);
- } else {
- discountActive.setVisibility(View.GONE);
- discountRegular.setVisibility(View.GONE);
- }
- } else {
- discountActive.setVisibility(View.GONE);
- discountRegular.setVisibility(View.GONE);
- }
- button.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- subscribe(s.getSku());
- }
- });
- if (anyPurchased) {
+ if (!showSolidButton) {
buttonView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
@@ -494,10 +459,6 @@ public abstract class ChoosePlanDialogFragment extends BaseOsmAndDialogFragment
if (div != null) {
div.setVisibility(View.GONE);
}
- View divBottom = lastBtn.findViewById(R.id.div_bottom);
- if (divBottom != null) {
- divBottom.setVisibility(View.GONE);
- }
}
if (osmLiveCardProgress != null) {
osmLiveCardProgress.setVisibility(View.GONE);
diff --git a/OsmAnd/src/net/osmand/plus/inapp/InAppPurchaseHelper.java b/OsmAnd/src/net/osmand/plus/inapp/InAppPurchaseHelper.java
index a1c04b63bc..9b4b26afd0 100644
--- a/OsmAnd/src/net/osmand/plus/inapp/InAppPurchaseHelper.java
+++ b/OsmAnd/src/net/osmand/plus/inapp/InAppPurchaseHelper.java
@@ -27,6 +27,7 @@ import net.osmand.plus.inapp.InAppPurchases.InAppPurchase;
import net.osmand.plus.inapp.InAppPurchases.InAppPurchase.PurchaseState;
import net.osmand.plus.inapp.InAppPurchases.InAppPurchaseLiveUpdatesOldSubscription;
import net.osmand.plus.inapp.InAppPurchases.InAppSubscription;
+import net.osmand.plus.inapp.InAppPurchases.InAppSubscriptionIntroductoryInfo;
import net.osmand.plus.inapp.InAppPurchases.InAppSubscriptionList;
import net.osmand.plus.inapp.util.BillingManager;
import net.osmand.plus.inapp.util.BillingManager.BillingUpdatesListener;
@@ -39,6 +40,7 @@ import org.json.JSONException;
import org.json.JSONObject;
import java.lang.ref.WeakReference;
+import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -603,7 +605,26 @@ public class InAppPurchaseHelper {
String subscriptionPeriod = skuDetails.getSubscriptionPeriod();
if (!Algorithms.isEmpty(subscriptionPeriod)) {
if (inAppPurchase instanceof InAppSubscription) {
- ((InAppSubscription) inAppPurchase).setSubscriptionPeriod(subscriptionPeriod);
+ try {
+ ((InAppSubscription) inAppPurchase).setSubscriptionPeriodString(subscriptionPeriod);
+ } catch (ParseException e) {
+ LOG.error(e);
+ }
+ }
+ }
+ if (inAppPurchase instanceof InAppSubscription) {
+ String introductoryPrice = skuDetails.getIntroductoryPrice();
+ String introductoryPricePeriod = skuDetails.getIntroductoryPricePeriod();
+ String introductoryPriceCycles = skuDetails.getIntroductoryPriceCycles();
+ long introductoryPriceAmountMicros = skuDetails.getIntroductoryPriceAmountMicros();
+ if (!Algorithms.isEmpty(introductoryPrice)) {
+ InAppSubscription s = (InAppSubscription) inAppPurchase;
+ try {
+ s.setIntroductoryInfo(new InAppSubscriptionIntroductoryInfo(s, introductoryPrice,
+ introductoryPriceAmountMicros, introductoryPricePeriod, introductoryPriceCycles));
+ } catch (ParseException e) {
+ LOG.error(e);
+ }
}
}
}
diff --git a/OsmAnd/src/net/osmand/plus/inapp/InAppPurchases.java b/OsmAnd/src/net/osmand/plus/inapp/InAppPurchases.java
index a321d56eea..90b73064d7 100644
--- a/OsmAnd/src/net/osmand/plus/inapp/InAppPurchases.java
+++ b/OsmAnd/src/net/osmand/plus/inapp/InAppPurchases.java
@@ -1,20 +1,28 @@
package net.osmand.plus.inapp;
import android.content.Context;
+import android.graphics.Typeface;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
+import android.support.v4.content.ContextCompat;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
-import android.text.style.StyleSpan;
+import android.text.style.ForegroundColorSpan;
import com.android.billingclient.api.SkuDetails;
+import net.osmand.AndroidUtils;
+import net.osmand.Period;
+import net.osmand.Period.PeriodUnit;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.R;
import net.osmand.plus.Version;
+import net.osmand.plus.helpers.FontCache;
+import net.osmand.plus.widgets.style.CustomTypefaceSpan;
import net.osmand.util.Algorithms;
import java.text.NumberFormat;
+import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Currency;
@@ -169,7 +177,7 @@ public class InAppPurchases {
private List subscriptions;
InAppSubscriptionList(@NonNull InAppSubscription[] subscriptionsArray) {
- this.subscriptions = Arrays.asList(subscriptionsArray);;
+ this.subscriptions = Arrays.asList(subscriptionsArray);
}
private List getSubscriptions() {
@@ -427,13 +435,204 @@ public class InAppPurchases {
}
}
+ public static class InAppSubscriptionIntroductoryInfo {
+
+ private InAppSubscription subscription;
+
+ private String introductoryPrice;
+ private long introductoryPriceAmountMicros;
+ private String introductoryPeriodString;
+ private int introductoryCycles;
+
+ private double introductoryPriceValue;
+ private Period introductoryPeriod;
+
+ public InAppSubscriptionIntroductoryInfo(@NonNull InAppSubscription subscription,
+ String introductoryPrice,
+ long introductoryPriceAmountMicros,
+ String introductoryPeriodString,
+ String introductoryCycles) throws ParseException {
+ this.subscription = subscription;
+ this.introductoryPrice = introductoryPrice;
+ this.introductoryPriceAmountMicros = introductoryPriceAmountMicros;
+ this.introductoryPeriodString = introductoryPeriodString;
+ try {
+ this.introductoryCycles = Integer.parseInt(introductoryCycles);
+ } catch (NumberFormatException e) {
+ throw new ParseException("Cannot parse introductoryCycles = " + introductoryCycles, 0);
+ }
+ introductoryPriceValue = introductoryPriceAmountMicros / 1000000d;
+ introductoryPeriod = Period.parse(introductoryPeriodString);
+ }
+
+ public String getIntroductoryPrice() {
+ return introductoryPrice;
+ }
+
+ public long getIntroductoryPriceAmountMicros() {
+ return introductoryPriceAmountMicros;
+ }
+
+ public String getIntroductoryPeriodString() {
+ return introductoryPeriodString;
+ }
+
+ public int getIntroductoryCycles() {
+ return introductoryCycles;
+ }
+
+ public double getIntroductoryPriceValue() {
+ return introductoryPriceValue;
+ }
+
+ public double getIntroductoryMonthlyPriceValue() {
+ return introductoryPriceValue /
+ (introductoryPeriod.getUnit().getMonthsValue() * introductoryPeriod.getNumberOfUnits());
+ }
+
+ public Period getIntroductoryPeriod() {
+ return introductoryPeriod;
+ }
+
+ public long getTotalPeriods() {
+ return introductoryPeriod.getNumberOfUnits() * introductoryCycles;
+ }
+
+ private String getTotalUnitsString(@NonNull Context ctx, boolean original) {
+ String unitStr = "";
+ Period subscriptionPeriod = subscription.getSubscriptionPeriod();
+ PeriodUnit unit = original && subscriptionPeriod != null ? subscriptionPeriod.getUnit() : introductoryPeriod.getUnit();
+ long totalPeriods = original && subscriptionPeriod != null ? subscriptionPeriod.getNumberOfUnits() : getTotalPeriods();
+ switch (unit) {
+ case YEAR:
+ unitStr = ctx.getString(R.string.year);
+ break;
+ case MONTH:
+ if (totalPeriods == 1) {
+ unitStr = ctx.getString(R.string.month);
+ } else if (totalPeriods < 5) {
+ unitStr = ctx.getString(R.string.months_2_4);
+ } else {
+ unitStr = ctx.getString(R.string.months_5);
+ }
+ break;
+ case WEEK:
+ if (totalPeriods == 1) {
+ unitStr = ctx.getString(R.string.week);
+ } else if (totalPeriods < 5) {
+ unitStr = ctx.getString(R.string.weeks_2_4);
+ } else {
+ unitStr = ctx.getString(R.string.weeks_5);
+ }
+ break;
+ case DAY:
+ if (totalPeriods == 1) {
+ unitStr = ctx.getString(R.string.day);
+ } else if (totalPeriods < 5) {
+ unitStr = ctx.getString(R.string.days_2_4);
+ } else {
+ unitStr = ctx.getString(R.string.days_5);
+ }
+ break;
+ }
+ return unitStr;
+ }
+
+ private String getUnitString(@NonNull Context ctx) {
+ PeriodUnit unit = introductoryPeriod.getUnit();
+ switch (unit) {
+ case YEAR:
+ return ctx.getString(R.string.year);
+ case MONTH:
+ return ctx.getString(R.string.month);
+ case WEEK:
+ return ctx.getString(R.string.week);
+ case DAY:
+ return ctx.getString(R.string.day);
+ }
+ return "";
+ }
+
+ private String getDisountPeriodString(String unitStr, long totalPeriods) {
+ if (totalPeriods == 1)
+ return unitStr;
+ if (AndroidUtils.isRTL()) {
+ return unitStr + " " + totalPeriods;
+ } else {
+ return totalPeriods + " " + unitStr;
+ }
+ }
+
+ public CharSequence getDescriptionTitle(@NonNull Context ctx) {
+ long totalPeriods = getTotalPeriods();
+ String unitStr = getTotalUnitsString(ctx, false).toLowerCase();
+ int discountPercent = subscription.getDiscountPercent(null);
+ return ctx.getString(R.string.get_discount_title, totalPeriods, unitStr, discountPercent + "%");
+ }
+
+ public CharSequence getFormattedDescription(@NonNull Context ctx) {
+ long totalPeriods = getTotalPeriods();
+ String singleUnitStr = getUnitString(ctx).toLowerCase();
+ String unitStr = getTotalUnitsString(ctx, false).toLowerCase();
+ long numberOfUnits = introductoryPeriod.getNumberOfUnits();
+ Period subscriptionPeriod = subscription.getSubscriptionPeriod();
+ long originalNumberOfUnits = subscriptionPeriod != null ? subscriptionPeriod.getNumberOfUnits() : 1;
+ String originalUnitsStr = getTotalUnitsString(ctx, true).toLowerCase();
+ String originalPriceStr = subscription.getPrice(ctx);
+ String priceStr = introductoryPrice;
+
+ String pricePeriod;
+ String originalPricePeriod;
+
+ if (AndroidUtils.isRTL()) {
+ pricePeriod = singleUnitStr + " / " + priceStr;
+ originalPricePeriod = originalUnitsStr + " / " + originalPriceStr;
+ if (numberOfUnits > 1) {
+ pricePeriod = unitStr + " " + numberOfUnits + " / " + priceStr;
+ }
+ if (originalNumberOfUnits == 3 && subscriptionPeriod.getUnit() == PeriodUnit.MONTH) {
+ originalPricePeriod = ctx.getString(R.string.months_3).toLowerCase() + " / " + originalPriceStr;
+ } else if (originalNumberOfUnits > 1) {
+ originalPricePeriod = originalUnitsStr + " " + originalNumberOfUnits + " / " + originalPriceStr;
+ }
+ } else {
+ pricePeriod = priceStr + " / " + singleUnitStr;
+ originalPricePeriod = originalPriceStr + " / " + originalUnitsStr;
+ if (numberOfUnits > 1) {
+ pricePeriod = priceStr + " / " + numberOfUnits + " " + unitStr;
+ }
+ if (originalNumberOfUnits == 3 && subscriptionPeriod.getUnit() == PeriodUnit.MONTH) {
+ originalPricePeriod = originalPriceStr + " / " + ctx.getString(R.string.months_3).toLowerCase();
+ } else if (originalNumberOfUnits > 1) {
+ originalPricePeriod = originalPriceStr + " / " + originalNumberOfUnits + " " + originalUnitsStr;
+ }
+ }
+ String periodPriceStr = introductoryCycles == 1 ? priceStr : pricePeriod;
+
+ int firstPartRes = totalPeriods == 1 ? R.string.get_discount_first_part : R.string.get_discount_first_few_part;
+ SpannableStringBuilder mainPart = new SpannableStringBuilder(ctx.getString(firstPartRes, periodPriceStr, getDisountPeriodString(unitStr, totalPeriods)));
+ SpannableStringBuilder thenPart = new SpannableStringBuilder(ctx.getString(R.string.get_discount_second_part, originalPricePeriod));
+ Typeface typefaceRegular = FontCache.getRobotoRegular(ctx);
+ Typeface typefaceBold = FontCache.getRobotoMedium(ctx);
+ mainPart.setSpan(new CustomTypefaceSpan(typefaceBold), 0, mainPart.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ int textColor = ContextCompat.getColor(ctx, R.color.white_50_transparent);
+ thenPart.setSpan(new ForegroundColorSpan(textColor), 0, thenPart.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ thenPart.setSpan(new CustomTypefaceSpan(typefaceRegular), 0, thenPart.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+ return new SpannableStringBuilder(mainPart).append("\n").append(thenPart);
+ }
+ }
+
public static abstract class InAppSubscription extends InAppPurchase {
private Map upgrades = new ConcurrentHashMap<>();
private String skuNoVersion;
- private String subscriptionPeriod;
+ private String subscriptionPeriodString;
+ private Period subscriptionPeriod;
private boolean upgrade = false;
+ private InAppSubscriptionIntroductoryInfo introductoryInfo;
+
InAppSubscription(@NonNull String skuNoVersion, int version) {
super(skuNoVersion + "_v" + version);
this.skuNoVersion = skuNoVersion;
@@ -482,12 +681,36 @@ public class InAppPurchases {
return skuNoVersion;
}
- public String getSubscriptionPeriod() {
+ @Nullable
+ public String getSubscriptionPeriodString() {
+ return subscriptionPeriodString;
+ }
+
+ @Nullable
+ public Period getSubscriptionPeriod() {
return subscriptionPeriod;
}
- public void setSubscriptionPeriod(String subscriptionPeriod) {
- this.subscriptionPeriod = subscriptionPeriod;
+ public void setSubscriptionPeriodString(String subscriptionPeriodString) throws ParseException {
+ this.subscriptionPeriodString = subscriptionPeriodString;
+ this.subscriptionPeriod = Period.parse(subscriptionPeriodString);
+ }
+
+ public InAppSubscriptionIntroductoryInfo getIntroductoryInfo() {
+ /*
+ try {
+ if (subscriptionPeriod != null && subscriptionPeriod.getUnit() == PeriodUnit.YEAR) {
+ introductoryInfo = new InAppSubscriptionIntroductoryInfo(this, "30 грн.", 30000000L, "P1Y", "1");
+ }
+ } catch (ParseException e) {
+ //
+ }
+ */
+ return introductoryInfo;
+ }
+
+ public void setIntroductoryInfo(InAppSubscriptionIntroductoryInfo introductoryInfo) {
+ this.introductoryInfo = introductoryInfo;
}
@Override
@@ -504,12 +727,34 @@ public class InAppPurchases {
}
}
+ public CharSequence getDescription(@NonNull Context ctx, @Nullable InAppSubscription monthlyLiveUpdates) {
+ CharSequence descr = getDescription(ctx);
+ int discountPercent = getDiscountPercent(monthlyLiveUpdates);
+ return discountPercent > 0 ? ctx.getString(R.string.price_and_discount, descr, discountPercent + "%") : descr;
+ }
+
public CharSequence getRenewDescription(@NonNull Context ctx) {
return "";
}
@Nullable
protected abstract InAppSubscription newInstance(@NonNull String sku);
+
+ public int getDiscountPercent(@Nullable InAppSubscription monthlyLiveUpdates) {
+ double monthlyPriceValue = getMonthlyPriceValue();
+ if (monthlyLiveUpdates != null) {
+ double regularMonthlyPrice = monthlyLiveUpdates.getPriceValue();
+ if (regularMonthlyPrice > 0 && monthlyPriceValue > 0 && monthlyPriceValue < regularMonthlyPrice) {
+ return (int) ((1 - monthlyPriceValue / regularMonthlyPrice) * 100d);
+ }
+ } else if (introductoryInfo != null) {
+ double introductoryMonthlyPrice = introductoryInfo.getIntroductoryMonthlyPriceValue();
+ if (introductoryMonthlyPrice > 0 && monthlyPriceValue > 0 && monthlyPriceValue > introductoryMonthlyPrice) {
+ return (int) ((1 - introductoryMonthlyPrice / monthlyPriceValue) * 100d);
+ }
+ }
+ return 0;
+ }
}
public static class InAppPurchaseFullVersion extends InAppPurchase {
@@ -623,14 +868,6 @@ public class InAppPurchases {
return ctx.getString(R.string.osm_live_payment_monthly_title);
}
- @Override
- public CharSequence getDescription(@NonNull Context ctx) {
- CharSequence descr = super.getDescription(ctx);
- SpannableStringBuilder text = new SpannableStringBuilder(descr).append(". ").append(ctx.getString(R.string.osm_live_payment_contribute_descr));
- text.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), descr.length() + 1, text.length() - 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
- return text;
- }
-
@Override
public CharSequence getRenewDescription(@NonNull Context ctx) {
return ctx.getString(R.string.osm_live_payment_renews_monthly);