diff --git a/OsmAnd/res/drawable/bg_osmand_live_active.xml b/OsmAnd/res/drawable/bg_osmand_live_active.xml
new file mode 100644
index 0000000000..8544f60004
--- /dev/null
+++ b/OsmAnd/res/drawable/bg_osmand_live_active.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/OsmAnd/res/drawable/bg_osmand_live_cancelled.xml b/OsmAnd/res/drawable/bg_osmand_live_cancelled.xml
new file mode 100644
index 0000000000..2774bc263a
--- /dev/null
+++ b/OsmAnd/res/drawable/bg_osmand_live_cancelled.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/OsmAnd/res/drawable/btn_solid_border_dark.xml b/OsmAnd/res/drawable/btn_solid_border_dark.xml
new file mode 100644
index 0000000000..5f0ecb368b
--- /dev/null
+++ b/OsmAnd/res/drawable/btn_solid_border_dark.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/OsmAnd/res/drawable/btn_solid_border_light.xml b/OsmAnd/res/drawable/btn_solid_border_light.xml
new file mode 100644
index 0000000000..528f174597
--- /dev/null
+++ b/OsmAnd/res/drawable/btn_solid_border_light.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/OsmAnd/res/layout/dialog_edit_gpx_description.xml b/OsmAnd/res/layout/dialog_edit_gpx_description.xml
index 66c0d57f0a..a2997960b9 100644
--- a/OsmAnd/res/layout/dialog_edit_gpx_description.xml
+++ b/OsmAnd/res/layout/dialog_edit_gpx_description.xml
@@ -45,7 +45,7 @@
osmand:typeface="@string/font_roboto_medium" />
-
+ android:layout_height="match_parent">
-
-
-
+ android:orientation="vertical">
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
-
+
-
+
-
+
-
+
-
+
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/OsmAnd/res/layout/subscription_layout.xml b/OsmAnd/res/layout/subscription_layout.xml
new file mode 100644
index 0000000000..de35b80f4a
--- /dev/null
+++ b/OsmAnd/res/layout/subscription_layout.xml
@@ -0,0 +1,108 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/OsmAnd/res/layout/subscriptions_card.xml b/OsmAnd/res/layout/subscriptions_card.xml
new file mode 100644
index 0000000000..278f381933
--- /dev/null
+++ b/OsmAnd/res/layout/subscriptions_card.xml
@@ -0,0 +1,5 @@
+
+
\ No newline at end of file
diff --git a/OsmAnd/res/values/colors.xml b/OsmAnd/res/values/colors.xml
index 410723dc79..8d9f9dc24a 100644
--- a/OsmAnd/res/values/colors.xml
+++ b/OsmAnd/res/values/colors.xml
@@ -75,6 +75,8 @@
#d28521
#237bff
#d28521
+ #14CC70
+ #EE5622
#505050
diff --git a/OsmAnd/res/values/strings.xml b/OsmAnd/res/values/strings.xml
index 1117181317..3e81d699c3 100644
--- a/OsmAnd/res/values/strings.xml
+++ b/OsmAnd/res/values/strings.xml
@@ -12,6 +12,16 @@
-->
+ Expired
+ On hold
+ In grace period
+ Renew subscription
+ Cancelled
+ Next billing date: %1$s
+ Three months subscription
+ Monthly subscription
+ Annual subscription
+ OsmAnd Live
Please follow this link if you any issues with purchases.
Troubleshooting
Contact support
diff --git a/OsmAnd/src/net/osmand/plus/inapp/InAppPurchases.java b/OsmAnd/src/net/osmand/plus/inapp/InAppPurchases.java
index e6ec431187..8a6d5a0128 100644
--- a/OsmAnd/src/net/osmand/plus/inapp/InAppPurchases.java
+++ b/OsmAnd/src/net/osmand/plus/inapp/InAppPurchases.java
@@ -8,8 +8,10 @@ import android.text.SpannableStringBuilder;
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.Period;
@@ -580,24 +582,40 @@ public abstract class InAppPurchases {
private InAppSubscriptionIntroductoryInfo introductoryInfo;
public enum SubscriptionState {
- UNDEFINED("undefined"),
- ACTIVE("active"),
- CANCELLED("cancelled"),
- IN_GRACE_PERIOD("in_grace_period"),
- ON_HOLD("on_hold"),
- PAUSED("paused"),
- EXPIRED("expired");
+ UNDEFINED("undefined", 0, 0),
+ ACTIVE("active", R.string.osm_live_active, R.drawable.bg_osmand_live_active),
+ CANCELLED("cancelled", R.string.osmand_live_cancelled, R.drawable.bg_osmand_live_cancelled),
+ IN_GRACE_PERIOD("in_grace_period", R.string.in_grace_period, R.drawable.bg_osmand_live_active),
+ ON_HOLD("on_hold", R.string.on_hold, R.drawable.bg_osmand_live_cancelled),
+ PAUSED("paused", R.string.shared_string_paused, R.drawable.bg_osmand_live_cancelled),
+ EXPIRED("expired", R.string.expired, R.drawable.bg_osmand_live_cancelled);
private final String stateStr;
+ @StringRes
+ private final int stringRes;
+ @DrawableRes
+ private final int backgroundRes;
- SubscriptionState(@NonNull String stateStr) {
+ SubscriptionState(@NonNull String stateStr, @StringRes int stringRes, @DrawableRes int backgroundRes) {
this.stateStr = stateStr;
+ this.stringRes = stringRes;
+ this.backgroundRes = backgroundRes;
}
public String getStateStr() {
return stateStr;
}
+ @StringRes
+ public int getStringRes() {
+ return stringRes;
+ }
+
+ @DrawableRes
+ public int getBackgroundRes() {
+ return backgroundRes;
+ }
+
@NonNull
public static SubscriptionState getByStateStr(@NonNull String stateStr) {
for (SubscriptionState state : SubscriptionState.values()) {
diff --git a/OsmAnd/src/net/osmand/plus/settings/fragments/PurchasesFragment.java b/OsmAnd/src/net/osmand/plus/settings/fragments/PurchasesFragment.java
index 9fb4047ca5..6b5a9c5d5f 100644
--- a/OsmAnd/src/net/osmand/plus/settings/fragments/PurchasesFragment.java
+++ b/OsmAnd/src/net/osmand/plus/settings/fragments/PurchasesFragment.java
@@ -38,11 +38,12 @@ import net.osmand.plus.activities.OsmandInAppPurchaseActivity;
import net.osmand.plus.base.BaseOsmAndFragment;
import net.osmand.plus.chooseplan.ChoosePlanDialogFragment;
import net.osmand.plus.inapp.InAppPurchaseHelper;
+import net.osmand.plus.inapp.InAppPurchaseHelper.InAppPurchaseListener;
import net.osmand.plus.liveupdates.LiveUpdatesFragmentNew;
import net.osmand.plus.liveupdates.OsmLiveActivity;
import net.osmand.plus.wikipedia.WikipediaDialogFragment;
-public class PurchasesFragment extends BaseOsmAndFragment {
+public class PurchasesFragment extends BaseOsmAndFragment implements InAppPurchaseListener {
public static final String TAG = PurchasesFragment.class.getName();
public static final String KEY_IS_SUBSCRIBER = "action_is_new";
@@ -50,6 +51,7 @@ public class PurchasesFragment extends BaseOsmAndFragment {
private static final String PLAY_STORE_SUBSCRIPTION_DEEPLINK_URL = "https://play.google.com/store/account/subscriptions?sku=%s&package=%s";
private InAppPurchaseHelper purchaseHelper;
private View mainView;
+ private SubscriptionsCard subscriptionsCard;
private Context context;
private OsmandApplication app;
private String url;
@@ -83,9 +85,14 @@ public class PurchasesFragment extends BaseOsmAndFragment {
final boolean nightMode = !getMyApplication().getSettings().isLightContent();
LayoutInflater themedInflater = UiUtilities.getInflater(context, nightMode);
- if (!isSubscriber) {
+ if (isSubscriber) {
mainView = themedInflater.inflate(R.layout.purchases_layout, container, false);
setSubscriptionClick(mapActivity);
+ if (mapActivity != null && purchaseHelper != null) {
+ ViewGroup subscriptionsCardContainer = mainView.findViewById(R.id.subscriptions_card_container);
+ subscriptionsCard = new SubscriptionsCard(mapActivity, purchaseHelper);
+ subscriptionsCardContainer.addView(subscriptionsCard.build(mapActivity));
+ }
} else {
mainView = themedInflater.inflate(R.layout.empty_purchases_layout, container, false);
LinearLayout osmandLive = mainView.findViewById(R.id.osmand_live);
@@ -114,7 +121,7 @@ public class PurchasesFragment extends BaseOsmAndFragment {
purchasesRestore.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- if (purchaseHelper != null && !purchaseHelper.hasInventory()) {
+ if (purchaseHelper != null) {
purchaseHelper.requestInventory();
}
}
@@ -255,4 +262,30 @@ public class PurchasesFragment extends BaseOsmAndFragment {
url = PLAY_STORE_SUBSCRIPTION_URL;
}
}
+
+ @Override
+ public void onError(InAppPurchaseHelper.InAppPurchaseTaskType taskType, String error) {
+ }
+
+ @Override
+ public void onGetItems() {
+ if (subscriptionsCard != null) {
+ subscriptionsCard.update();
+ }
+ }
+
+ @Override
+ public void onItemPurchased(String sku, boolean active) {
+ if (purchaseHelper != null) {
+ purchaseHelper.requestInventory();
+ }
+ }
+
+ @Override
+ public void showProgress(InAppPurchaseHelper.InAppPurchaseTaskType taskType) {
+ }
+
+ @Override
+ public void dismissProgress(InAppPurchaseHelper.InAppPurchaseTaskType taskType) {
+ }
}
diff --git a/OsmAnd/src/net/osmand/plus/settings/fragments/SubscriptionsCard.java b/OsmAnd/src/net/osmand/plus/settings/fragments/SubscriptionsCard.java
new file mode 100644
index 0000000000..40ef081602
--- /dev/null
+++ b/OsmAnd/src/net/osmand/plus/settings/fragments/SubscriptionsCard.java
@@ -0,0 +1,179 @@
+package net.osmand.plus.settings.fragments;
+
+import android.os.Build;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import net.osmand.AndroidUtils;
+import net.osmand.Period;
+import net.osmand.plus.R;
+import net.osmand.plus.activities.MapActivity;
+import net.osmand.plus.helpers.AndroidUiHelper;
+import net.osmand.plus.inapp.InAppPurchaseHelper;
+import net.osmand.plus.inapp.InAppPurchases.InAppSubscription;
+import net.osmand.plus.inapp.InAppPurchases.InAppSubscription.SubscriptionState;
+import net.osmand.plus.routepreparationmenu.cards.BaseCard;
+import net.osmand.plus.settings.backend.OsmandSettings;
+import net.osmand.util.Algorithms;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.view.ContextThemeWrapper;
+import androidx.core.content.ContextCompat;
+
+public class SubscriptionsCard extends BaseCard {
+
+ private final InAppPurchaseHelper purchaseHelper;
+
+ @Override
+ public int getCardLayoutId() {
+ return R.layout.subscriptions_card;
+ }
+
+ public SubscriptionsCard(@NonNull MapActivity mapActivity, @NonNull InAppPurchaseHelper purchaseHelper) {
+ super(mapActivity);
+ this.purchaseHelper = purchaseHelper;
+ }
+
+ @Override
+ protected void updateContent() {
+ if (mapActivity == null) {
+ return;
+ }
+
+ List subscriptions = getActiveAndCancelledSubscriptions();
+ if (Algorithms.isEmpty(subscriptions)) {
+ return;
+ }
+
+ ContextThemeWrapper ctx = new ContextThemeWrapper(mapActivity, !nightMode ? R.style.OsmandLightTheme : R.style.OsmandDarkTheme);
+ LayoutInflater inflater = LayoutInflater.from(ctx);
+ ((ViewGroup) view).removeAllViews();
+
+ for (int i = 0; i < subscriptions.size(); i++) {
+ InAppSubscription subscription = subscriptions.get(i);
+ SubscriptionState state = subscription.getState();
+ boolean autoRenewed = SubscriptionState.ACTIVE.equals(state) || SubscriptionState.IN_GRACE_PERIOD.equals(state);
+
+ View card = inflater.inflate(R.layout.subscription_layout, null, false);
+ ((ViewGroup) view).addView(card);
+
+ TextView subscriptionPeriod = card.findViewById(R.id.subscription_type);
+ String period = getSubscriptionPeriod(subscription.getSubscriptionPeriod());
+ if (!Algorithms.isEmpty(period)) {
+ subscriptionPeriod.setText(period);
+ AndroidUiHelper.updateVisibility(subscriptionPeriod, true);
+ }
+
+ if (autoRenewed) {
+ TextView nextBillingDate = card.findViewById(R.id.next_billing_date);
+ String date = getHumanDate(subscription.getPurchaseTime(), subscription.getSubscriptionPeriod());
+ if (!Algorithms.isEmpty(date)) {
+ nextBillingDate.setText(app.getString(R.string.next_billing_date, date));
+ AndroidUiHelper.updateVisibility(nextBillingDate, true);
+ }
+ }
+
+ TextView status = card.findViewById(R.id.status);
+ status.setText(app.getString(state.getStringRes()));
+ status.setBackgroundDrawable(ContextCompat.getDrawable(mapActivity, state.getBackgroundRes()));
+
+ if (!autoRenewed) {
+ View renewContainer = card.findViewById(R.id.renewContainer);
+ AndroidUiHelper.updateVisibility(renewContainer, true);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ AndroidUtils.setBackground(ctx, renewContainer, nightMode, R.drawable.ripple_light, R.drawable.ripple_dark);
+ } else {
+ AndroidUtils.setBackground(ctx, renewContainer, nightMode, R.drawable.btn_unstroked_light, R.drawable.btn_unstroked_dark);
+ }
+ final String sku = subscription.getSku();
+ renewContainer.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ subscribe(sku);
+ }
+ });
+
+ View renew = card.findViewById(R.id.renew);
+ AndroidUtils.setBackground(ctx, renew, nightMode,
+ R.drawable.btn_solid_border_light, R.drawable.btn_solid_border_dark);
+ }
+
+ int dividerLayout = i + 1 == subscriptions.size() ? R.layout.simple_divider_item : R.layout.divider_half_item;
+ View divider = inflater.inflate(dividerLayout, (ViewGroup) view, false);
+ ((ViewGroup) view).addView(divider);
+ }
+ }
+
+ private String getHumanDate(long time, Period period) {
+ Date date = new Date(time);
+ int monthsCount;
+ if (period == null || period.getUnit() == null) {
+ return "";
+ } else if (period.getUnit().equals(Period.PeriodUnit.YEAR)) {
+ monthsCount = 12;
+ } else {
+ monthsCount = period.getNumberOfUnits();
+ }
+ Calendar calendar = Calendar.getInstance();
+ calendar.setTime(date);
+ calendar.add(Calendar.MONTH, monthsCount);
+ date = calendar.getTime();
+ SimpleDateFormat format = new SimpleDateFormat("MMM d, yyyy", app.getLocaleHelper().getPreferredLocale());
+ return format.format(date);
+ }
+
+ private void subscribe(String sku) {
+ if (app == null) {
+ return;
+ }
+ if (!app.getSettings().isInternetConnectionAvailable(true)) {
+ Toast.makeText(app, R.string.internet_not_available, Toast.LENGTH_LONG).show();
+ } else if (mapActivity != null && purchaseHelper != null) {
+ OsmandSettings settings = app.getSettings();
+ purchaseHelper.purchaseLiveUpdates(mapActivity, sku,
+ settings.BILLING_USER_EMAIL.get(),
+ settings.BILLING_USER_NAME.get(),
+ settings.BILLING_USER_COUNTRY_DOWNLOAD_NAME.get(),
+ settings.BILLING_HIDE_USER_NAME.get());
+ }
+ }
+
+ private String getSubscriptionPeriod(Period period) {
+ if (period == null || period.getUnit() == null) {
+ return "";
+ } else if (period.getUnit().equals(Period.PeriodUnit.YEAR)) {
+ return app.getString(R.string.annual_subscription);
+ } else if (period.getUnit().equals(Period.PeriodUnit.MONTH)) {
+ int unitsNumber = period.getNumberOfUnits();
+ if (unitsNumber == 1) {
+ return app.getString(R.string.monthly_subscription);
+ } else if (unitsNumber == 3) {
+ return app.getString(R.string.three_months_subscription);
+ }
+ }
+ return "";
+ }
+
+ private List getActiveAndCancelledSubscriptions() {
+ List subscriptions = new ArrayList<>();
+ for (InAppSubscription subscription : purchaseHelper.getLiveUpdates().getVisibleSubscriptions()) {
+ if (shouldShowSubscription(subscription)) {
+ subscriptions.add(subscription);
+ }
+ }
+ return subscriptions;
+ }
+
+ private boolean shouldShowSubscription(InAppSubscription s) {
+ return s.getState() != null && !SubscriptionState.UNDEFINED.equals(s.getState());
+ }
+}
\ No newline at end of file
diff --git a/OsmAnd/src/net/osmand/plus/track/GpxEditDescriptionDialogFragment.java b/OsmAnd/src/net/osmand/plus/track/GpxEditDescriptionDialogFragment.java
index b8ef1c24d2..52536316b8 100644
--- a/OsmAnd/src/net/osmand/plus/track/GpxEditDescriptionDialogFragment.java
+++ b/OsmAnd/src/net/osmand/plus/track/GpxEditDescriptionDialogFragment.java
@@ -110,9 +110,8 @@ public class GpxEditDescriptionDialogFragment extends BaseOsmAndDialogFragment {
}
private void setupSaveButton(View view) {
- View btnSave = view.findViewById(R.id.btn_save);
-
- btnSave.setOnClickListener(new View.OnClickListener() {
+ View btnSaveContainer = view.findViewById(R.id.btn_save_container);
+ btnSaveContainer.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Editable editable = editableHtml.getText();
@@ -122,12 +121,16 @@ public class GpxEditDescriptionDialogFragment extends BaseOsmAndDialogFragment {
}
});
- Context ctx = btnSave.getContext();
+ Context ctx = btnSaveContainer.getContext();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- AndroidUtils.setBackground(ctx, btnSave, isNightMode(true), R.drawable.ripple_light, R.drawable.ripple_dark);
+ AndroidUtils.setBackground(ctx, btnSaveContainer, isNightMode(true), R.drawable.ripple_light, R.drawable.ripple_dark);
} else {
- AndroidUtils.setBackground(ctx, btnSave, isNightMode(true), R.drawable.btn_unstroked_light, R.drawable.btn_unstroked_dark);
+ AndroidUtils.setBackground(ctx, btnSaveContainer, isNightMode(true), R.drawable.btn_unstroked_light, R.drawable.btn_unstroked_dark);
}
+
+ View btnSave = view.findViewById(R.id.btn_save);
+ int drawableRes = isNightMode(true) ? R.drawable.btn_solid_border_dark : R.drawable.btn_solid_border_light;
+ btnSave.setBackgroundDrawable(ContextCompat.getDrawable(ctx, drawableRes));
}
private void showDismissDialog() {