From c65a276c63966ec01d7ba0bc08abd4bd163c2445 Mon Sep 17 00:00:00 2001 From: sictiru Date: Sun, 30 Aug 2020 16:57:15 -0700 Subject: [PATCH] #1343 Premium Account --- .../NewsBlur/res/layout/activity_premium.xml | 455 ++++++++++-------- clients/android/NewsBlur/res/menu/main.xml | 4 + .../android/NewsBlur/res/values/strings.xml | 6 +- .../src/com/newsblur/activity/Main.java | 4 + .../src/com/newsblur/activity/Premium.java | 107 +++- .../com/newsblur/network/APIConstants.java | 3 + .../src/com/newsblur/network/APIManager.java | 8 + .../src/com/newsblur/util/AppConstants.java | 3 + 8 files changed, 353 insertions(+), 237 deletions(-) diff --git a/clients/android/NewsBlur/res/layout/activity_premium.xml b/clients/android/NewsBlur/res/layout/activity_premium.xml index 72d500dec..ea58091b8 100644 --- a/clients/android/NewsBlur/res/layout/activity_premium.xml +++ b/clients/android/NewsBlur/res/layout/activity_premium.xml @@ -1,253 +1,288 @@ - + android:animateLayoutChanges="true"> - + android:orientation="vertical" + android:padding="16dp"> - - - - - - - - - + android:gravity="center_horizontal" + android:text="@string/premium_title_going_premium" + android:textSize="18sp" + android:textStyle="bold" /> + + + + + + + + + + + + + + + + + + + + + + + + + android:layout_marginStart="40dp" + android:text="@string/premium_sync" /> + + + + + + + android:layout_marginStart="40dp" + android:text="@string/premium_read_by_folder" /> - + - + android:layout_marginTop="16dp"> - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + android:id="@+id/img_shiloh" + android:layout_width="104dp" + android:layout_height="104dp" + android:layout_gravity="center_horizontal" + android:layout_marginTop="16dp" + android:scaleType="centerCrop" /> + + + + - - - - - - + android:layout_marginTop="120dp" + android:gravity="center_horizontal" + android:text="@string/premium_title_gone_premium" + android:textSize="40sp" + android:textStyle="bold" /> + android:layout_marginTop="320dp" + android:gravity="center_horizontal" + android:lineSpacingExtra="@dimen/extra_line_spacing" + android:textSize="18sp" + android:visibility="gone" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/clients/android/NewsBlur/res/menu/main.xml b/clients/android/NewsBlur/res/menu/main.xml index 521f11387..088d3925a 100644 --- a/clients/android/NewsBlur/res/menu/main.xml +++ b/clients/android/NewsBlur/res/menu/main.xml @@ -43,6 +43,10 @@ android:title="@string/menu_loginas" android:showAsAction="never" android:visible="false"/> + + Create a feedback post Email a bug report Theme… + Premium Account Add new folder icon @@ -264,7 +265,10 @@ Search is only available to premium subscribers NewsBlur Premium - Thank you so much for going premium! + Thank you so much for going premium! + Thank you for going premium! + Your premium subscription is set to\nrenew on %s + Your premium subscription is set\nto expire on %s Upgrading to a NewsBlur premium subscription gives you all of these features. Payments will be charged to your Play Store account at confirmation of purchase. Subscription renew unless auto-renew is turned off at least 24 hours before the end of the current period. Cancel at any time from Account Settings in Play Store. privacy policy and terms of use for details.]]> NewsBlur Premium Subscription diff --git a/clients/android/NewsBlur/src/com/newsblur/activity/Main.java b/clients/android/NewsBlur/src/com/newsblur/activity/Main.java index 08e1d1a0f..96a8d7491 100644 --- a/clients/android/NewsBlur/src/com/newsblur/activity/Main.java +++ b/clients/android/NewsBlur/src/com/newsblur/activity/Main.java @@ -373,6 +373,10 @@ public class Main extends NbActivity implements StateChangedListener, SwipeRefre } else if (item.getItemId() == R.id.menu_theme_black) { PrefsUtils.setSelectedTheme(this, ThemeValue.BLACK); UIUtils.restartActivity(this); + } else if (item.getItemId() == R.id.menu_premium_account) { + Intent intent = new Intent(this, Premium.class); + startActivity(intent); + return true; } return false; } diff --git a/clients/android/NewsBlur/src/com/newsblur/activity/Premium.java b/clients/android/NewsBlur/src/com/newsblur/activity/Premium.java index f4556b71f..3e5e41abd 100644 --- a/clients/android/NewsBlur/src/com/newsblur/activity/Premium.java +++ b/clients/android/NewsBlur/src/com/newsblur/activity/Premium.java @@ -2,9 +2,10 @@ package com.newsblur.activity; import android.graphics.Paint; import android.net.Uri; +import android.os.AsyncTask; import android.os.Bundle; +import android.text.format.DateUtils; import android.text.util.Linkify; -import android.util.Log; import android.view.View; import androidx.annotation.NonNull; @@ -17,33 +18,40 @@ import com.android.billingclient.api.BillingClientStateListener; import com.android.billingclient.api.BillingFlowParams; import com.android.billingclient.api.BillingResult; import com.android.billingclient.api.Purchase; -import com.android.billingclient.api.PurchaseHistoryRecord; -import com.android.billingclient.api.PurchaseHistoryResponseListener; import com.android.billingclient.api.PurchasesUpdatedListener; import com.android.billingclient.api.SkuDetails; import com.android.billingclient.api.SkuDetailsParams; import com.newsblur.R; import com.newsblur.databinding.ActivityPremiumBinding; +import com.newsblur.network.APIManager; +import com.newsblur.network.domain.NewsBlurResponse; +import com.newsblur.service.NBSyncService; import com.newsblur.util.AppConstants; import com.newsblur.util.BetterLinkMovementMethod; import com.newsblur.util.FeedUtils; +import com.newsblur.util.Log; +import com.newsblur.util.PrefsUtils; import com.newsblur.util.UIUtils; +import java.text.DateFormat; +import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Date; import java.util.List; import java.util.Locale; +import java.util.TimeZone; public class Premium extends NbActivity { private ActivityPremiumBinding binding; private BillingClient billingClient; - private final String subscriptionSku = "nb.premium.36"; private SkuDetails subscriptionDetails; + private Purchase purchasedSubscription; private AcknowledgePurchaseResponseListener acknowledgePurchaseResponseListener = billingResult -> { if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) { Log.d(Premium.this.getLocalClassName(), "acknowledgePurchaseResponseListener OK"); - enablePremiumAccess(); + verifyUserSubscriptionStatus(); } else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.BILLING_UNAVAILABLE) { // Billing API version is not supported for the type requested. Log.d(Premium.this.getLocalClassName(), "acknowledgePurchaseResponseListener BILLING_UNAVAILABLE"); @@ -83,20 +91,8 @@ public class Premium extends NbActivity { if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) { // The BillingClient is ready. You can query purchases here. Log.d(Premium.this.getLocalClassName(), "onBillingSetupFinished OK"); - retrieveSubs(); - Purchase.PurchasesResult result = billingClient.queryPurchases(BillingClient.SkuType.SUBS); - for (Purchase purchase : result.getPurchasesList()) { - Purchase purchase1 = purchase; - } - billingClient.queryPurchaseHistoryAsync(BillingClient.SkuType.SUBS, new PurchaseHistoryResponseListener() { - @Override - public void onPurchaseHistoryResponse(@NonNull BillingResult billingResult, @Nullable List list) { - Log.d("fdsfsdfsd", "fdsfdfsdfsd"); - for (PurchaseHistoryRecord purchaseHistoryRecord : list) { - purchaseHistoryRecord.getSku(); - } - } - }); + retrievePlayStoreSubscriptions(); + verifyUserSubscriptionStatus(); } else { showSubscriptionDetailsError(); } @@ -144,10 +140,48 @@ public class Premium extends NbActivity { billingClient.startConnection(billingClientStateListener); } - private void retrieveSubs() { + private void verifyUserSubscriptionStatus() { + boolean hasNewsBlurSubscription = PrefsUtils.isPremium(this); + Purchase playStoreSubscription = null; + Purchase.PurchasesResult result = billingClient.queryPurchases(BillingClient.SkuType.SUBS); + if (result.getPurchasesList() != null) { + for (Purchase purchase : result.getPurchasesList()) { + if (purchase.getSku().equals(AppConstants.PREMIUM_SKU)) { + playStoreSubscription = purchase; + } + } + } + + if (hasNewsBlurSubscription || playStoreSubscription != null) { + binding.containerGoingPremium.setVisibility(View.GONE); + binding.containerGonePremium.setVisibility(View.VISIBLE); + + if (playStoreSubscription != null) { + long expirationTimeMs = playStoreSubscription.getPurchaseTime() + DateUtils.YEAR_IN_MILLIS; + Date expirationDate = new Date(expirationTimeMs); + DateFormat dateFormat = new SimpleDateFormat("EEE, MMMM d, yyyy", Locale.getDefault()); + dateFormat.setTimeZone(TimeZone.getDefault()); + String renewalString; + if (playStoreSubscription.isAutoRenewing()) { + renewalString = getString(R.string.premium_subscription_renewal, dateFormat.format(expirationDate)); + } else { + renewalString = getString(R.string.premium_subscription_expiration, dateFormat.format(expirationDate)); + } + binding.textSubscriptionRenewal.setText(renewalString); + binding.textSubscriptionRenewal.setVisibility(View.VISIBLE); + } + } + + if (!hasNewsBlurSubscription && playStoreSubscription != null) { + purchasedSubscription = playStoreSubscription; + notifyNewsBlurOfSubscription(); + } + } + + private void retrievePlayStoreSubscriptions() { List skuList = new ArrayList<>(1); // add sub SKUs from Play Store - skuList.add(subscriptionSku); + skuList.add(AppConstants.PREMIUM_SKU); SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder(); params.setSkusList(skuList).setType(BillingClient.SkuType.SUBS); billingClient.querySkuDetailsAsync(params.build(), (billingResult, skuDetailsList) -> { @@ -159,7 +193,7 @@ public class Premium extends NbActivity { private void processSkuDetailsList(@Nullable List skuDetailsList) { if (skuDetailsList != null) { for (SkuDetails skuDetails : skuDetailsList) { - if (skuDetails.getSku().equals(subscriptionSku)) { + if (skuDetails.getSku().equals(AppConstants.PREMIUM_SKU)) { Log.d(Premium.this.getLocalClassName(), "Sku detail: " + skuDetails.getTitle() + " | " + skuDetails.getDescription() + " | " + skuDetails.getPrice() + " | " + skuDetails.getSku()); subscriptionDetails = skuDetails; } @@ -206,10 +240,13 @@ public class Premium extends NbActivity { billingClient.launchBillingFlow(this, billingFlowParams); } - private void handlePurchase(Purchase purchase) { Log.d(Premium.this.getLocalClassName(), "handlePurchase: " + purchase.getOrderId()); - if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED && !purchase.isAcknowledged()) { + purchasedSubscription = purchase; + if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED && purchase.isAcknowledged()) { + verifyUserSubscriptionStatus(); + } else if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED && !purchase.isAcknowledged()) { + // need to acknowledge first time sub otherwise it will void Log.d(Premium.this.getLocalClassName(), "acknowledge purchase: " + purchase.getOrderId()); AcknowledgePurchaseParams acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder() @@ -219,7 +256,25 @@ public class Premium extends NbActivity { } } - private void enablePremiumAccess() { - // what now? + private void notifyNewsBlurOfSubscription() { + if (purchasedSubscription != null) { + APIManager apiManager = new APIManager(this); + new AsyncTask() { + @Override + protected NewsBlurResponse doInBackground(Void... voids) { + return apiManager.saveReceipt(purchasedSubscription.getOrderId(), purchasedSubscription.getSku()); + } + + @Override + protected void onPostExecute(NewsBlurResponse result) { + super.onPostExecute(result); + if (!result.isError()) { + NBSyncService.forceFeedsFolders(); + triggerSync(); + } + finish(); + } + }.execute(); + } } } diff --git a/clients/android/NewsBlur/src/com/newsblur/network/APIConstants.java b/clients/android/NewsBlur/src/com/newsblur/network/APIConstants.java index c87d92087..52acf033e 100644 --- a/clients/android/NewsBlur/src/com/newsblur/network/APIConstants.java +++ b/clients/android/NewsBlur/src/com/newsblur/network/APIConstants.java @@ -76,6 +76,7 @@ public class APIConstants { public static final String PATH_ADD_FOLDER = "/reader/add_folder"; public static final String PATH_DELETE_FOLDER = "/reader/delete_folder"; public static final String PATH_RENAME_FOLDER = "/reader/rename_folder"; + public static final String PATH_SAVE_RECEIPT = "/profile/save_android_receipt"; public static String buildUrl(String path) { return CurrentUrlBase + path; @@ -129,6 +130,8 @@ public class APIConstants { public static final String PARAMETER_FOLDER_TO_DELETE = "folder_to_delete"; public static final String PARAMETER_FOLDER_TO_RENAME = "folder_to_rename"; public static final String PARAMETER_NEW_FOLDER_NAME = "new_folder_name"; + public static final String PARAMETER_ORDER_ID = "order_id"; + public static final String PARAMETER_PRODUCT_ID = "product_id"; public static final String VALUE_PREFIX_SOCIAL = "social:"; public static final String VALUE_ALLSOCIAL = "river:blurblogs"; // the magic value passed to the mark-read API for all social feeds diff --git a/clients/android/NewsBlur/src/com/newsblur/network/APIManager.java b/clients/android/NewsBlur/src/com/newsblur/network/APIManager.java index bb50282b3..dc1046037 100644 --- a/clients/android/NewsBlur/src/com/newsblur/network/APIManager.java +++ b/clients/android/NewsBlur/src/com/newsblur/network/APIManager.java @@ -661,6 +661,14 @@ public class APIManager { return response.getResponse(gson, NewsBlurResponse.class); } + public NewsBlurResponse saveReceipt(String orderId, String productId) { + ContentValues values = new ContentValues(); + values.put(APIConstants.PARAMETER_ORDER_ID, orderId); + values.put(APIConstants.PARAMETER_PRODUCT_ID, productId); + APIResponse response = post(buildUrl(APIConstants.PATH_SAVE_RECEIPT), values); + return response.getResponse(gson, NewsBlurResponse.class); + } + /* HTTP METHODS */ private APIResponse get(final String urlString) { diff --git a/clients/android/NewsBlur/src/com/newsblur/util/AppConstants.java b/clients/android/NewsBlur/src/com/newsblur/util/AppConstants.java index 80022b816..04d5f4806 100644 --- a/clients/android/NewsBlur/src/com/newsblur/util/AppConstants.java +++ b/clients/android/NewsBlur/src/com/newsblur/util/AppConstants.java @@ -95,4 +95,7 @@ public class AppConstants { // Shiloh photo public final static String SHILOH_PHOTO_URL = "https://newsblur.com/media//img/reader/shiloh.jpg"; + // Premium subscription SKU + public final static String PREMIUM_SKU = "nb.premium.36"; + }