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() {