mirror of
https://github.com/viq/NewsBlur.git
synced 2025-09-18 21:43:31 +00:00
#1343 Subscription details
This commit is contained in:
parent
cd618b0d57
commit
9121a8303a
7 changed files with 168 additions and 56 deletions
|
@ -6,6 +6,7 @@
|
|||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:animateLayoutChanges="true"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
|
@ -38,7 +39,6 @@
|
|||
android:textSize="12sp" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/container_purchase"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="40dp">
|
||||
|
@ -50,13 +50,15 @@
|
|||
android:src="@drawable/ic_hand_pointing_right" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/container_sub"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="40dp"
|
||||
android:orientation="vertical">
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_subscription_title"
|
||||
android:id="@+id/text_sub_title"
|
||||
style="?linkText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -65,6 +67,7 @@
|
|||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_sub_price"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
|
@ -75,6 +78,14 @@
|
|||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_loading"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:text="@string/loading"
|
||||
android:textSize="18sp" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<FrameLayout
|
||||
|
|
|
@ -269,6 +269,7 @@
|
|||
<string name="premium_policies"><![CDATA[See NewsBlur\'s <a href="https://newsblur.com/privacy">privacy policy</a> and <a href="https://newsblur.com/tos">terms of use</a> for details.]]></string>
|
||||
<string name="premium_subscription_title">NewsBlur Premium Subscription</string>
|
||||
<string name="premium_subscription_price">$35.99 per year ($3.00/month)</string>
|
||||
<string name="premium_subscription_details_error">Error retrieving subscription details</string>
|
||||
<string name="premium_sync">Sites updated up to 10x more often</string>
|
||||
<string name="premium_read_by_folder">River of News (reading by folder)</string>
|
||||
<string name="premium_search">Search sites and folders</string>
|
||||
|
|
|
@ -353,8 +353,7 @@ public abstract class ItemsList extends NbActivity implements StoryOrderChangedL
|
|||
if (q.length() < 1) {
|
||||
updateFleuron(false);
|
||||
q = null;
|
||||
//TODO: change flag
|
||||
} else if (PrefsUtils.isPremium(this)) {
|
||||
} else if (!PrefsUtils.isPremium(this)) {
|
||||
updateFleuron(true);
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -5,14 +5,20 @@ import android.net.Uri;
|
|||
import android.os.Bundle;
|
||||
import android.text.util.Linkify;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.billingclient.api.AcknowledgePurchaseParams;
|
||||
import com.android.billingclient.api.AcknowledgePurchaseResponseListener;
|
||||
import com.android.billingclient.api.BillingClient;
|
||||
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;
|
||||
|
@ -25,11 +31,85 @@ import com.newsblur.util.UIUtils;
|
|||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
public class Premium extends NbActivity {
|
||||
|
||||
private ActivityPremiumBinding binding;
|
||||
private BillingClient billingClient;
|
||||
private final String subscriptionSku = "nb.premium.36";
|
||||
private SkuDetails subscriptionDetails;
|
||||
|
||||
private AcknowledgePurchaseResponseListener acknowledgePurchaseResponseListener = billingResult -> {
|
||||
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
|
||||
Log.d(Premium.this.getLocalClassName(), "acknowledgePurchaseResponseListener OK");
|
||||
enablePremiumAccess();
|
||||
} 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");
|
||||
} else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.SERVICE_UNAVAILABLE) {
|
||||
// Network connection is down.
|
||||
Log.d(Premium.this.getLocalClassName(), "acknowledgePurchaseResponseListener SERVICE_UNAVAILABLE");
|
||||
} else {
|
||||
// Handle any other error codes.
|
||||
Log.d(Premium.this.getLocalClassName(), "acknowledgePurchaseResponseListener ERROR - message: " + billingResult.getDebugMessage());
|
||||
}
|
||||
};
|
||||
|
||||
private PurchasesUpdatedListener purchaseUpdateListener = (billingResult, purchases) -> {
|
||||
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && purchases != null) {
|
||||
Log.d(Premium.this.getLocalClassName(), "purchaseUpdateListener OK");
|
||||
for (Purchase purchase : purchases) {
|
||||
handlePurchase(purchase);
|
||||
}
|
||||
} else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.USER_CANCELED) {
|
||||
// Handle an error caused by a user cancelling the purchase flow.
|
||||
Log.d(Premium.this.getLocalClassName(), "purchaseUpdateListener USER_CANCELLED");
|
||||
} else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.BILLING_UNAVAILABLE) {
|
||||
// Billing API version is not supported for the type requested.
|
||||
Log.d(Premium.this.getLocalClassName(), "purchaseUpdateListener BILLING_UNAVAILABLE");
|
||||
} else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.SERVICE_UNAVAILABLE) {
|
||||
// Network connection is down.
|
||||
Log.d(Premium.this.getLocalClassName(), "purchaseUpdateListener SERVICE_UNAVAILABLE");
|
||||
} else {
|
||||
// Handle any other error codes.
|
||||
Log.d(Premium.this.getLocalClassName(), "purchaseUpdateListener ERROR - message: " + billingResult.getDebugMessage());
|
||||
}
|
||||
};
|
||||
|
||||
private BillingClientStateListener billingClientStateListener = new BillingClientStateListener() {
|
||||
@Override
|
||||
public void onBillingSetupFinished(@NonNull BillingResult billingResult) {
|
||||
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<PurchaseHistoryRecord> list) {
|
||||
Log.d("fdsfsdfsd", "fdsfdfsdfsd");
|
||||
for (PurchaseHistoryRecord purchaseHistoryRecord : list) {
|
||||
purchaseHistoryRecord.getSku();
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
showSubscriptionDetailsError();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBillingServiceDisconnected() {
|
||||
Log.d(Premium.this.getLocalClassName(), "onBillingServiceDisconnected");
|
||||
// Try to restart the connection on the next request to
|
||||
// Google Play by calling the startConnection() method.
|
||||
showSubscriptionDetailsError();
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle bundle) {
|
||||
|
@ -51,73 +131,95 @@ public class Premium extends NbActivity {
|
|||
return true;
|
||||
});
|
||||
binding.textPolicies.setText(UIUtils.fromHtml(getString(R.string.premium_policies)));
|
||||
binding.textSubscriptionTitle.setPaintFlags(binding.textSubscriptionTitle.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG);
|
||||
binding.textSubTitle.setPaintFlags(binding.textSubTitle.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG);
|
||||
FeedUtils.iconLoader.displayImage(AppConstants.SHILOH_PHOTO_URL, binding.imgShiloh, 0, false);
|
||||
}
|
||||
|
||||
private void setupBillingClient() {
|
||||
PurchasesUpdatedListener purchaseUpdateListener = (billingResult, purchases) -> {
|
||||
// To be implemented
|
||||
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && purchases != null) {
|
||||
for (Purchase purchase : purchases) {
|
||||
// handle purchase
|
||||
}
|
||||
} else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.USER_CANCELED) {
|
||||
// Handle an error caused by a user cancelling the purchase flow.
|
||||
} else {
|
||||
// Handle any other error codes.
|
||||
}
|
||||
};
|
||||
|
||||
billingClient = BillingClient.newBuilder(this)
|
||||
.setListener(purchaseUpdateListener)
|
||||
.enablePendingPurchases()
|
||||
.build();
|
||||
|
||||
billingClient.startConnection(new BillingClientStateListener() {
|
||||
@Override
|
||||
public void onBillingSetupFinished(@NonNull BillingResult billingResult) {
|
||||
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
|
||||
// The BillingClient is ready. You can query purchases here.
|
||||
Log.d(Premium.this.getLocalClassName(), "onBillingSetupFinished - message: " + billingResult.getDebugMessage() + " | response code: " + billingResult.getResponseCode());
|
||||
}
|
||||
}
|
||||
billingClient.startConnection(billingClientStateListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBillingServiceDisconnected() {
|
||||
Log.d(Premium.this.getLocalClassName(), "onBillingServiceDisconnected");
|
||||
// Try to restart the connection on the next request to
|
||||
// Google Play by calling the startConnection() method.
|
||||
}
|
||||
});
|
||||
|
||||
List<String> skuList = new ArrayList<>();
|
||||
private void retrieveSubs() {
|
||||
List<String> skuList = new ArrayList<>(1);
|
||||
// add sub SKUs from Play Store
|
||||
skuList.add("premium_subscription");
|
||||
skuList.add(subscriptionSku);
|
||||
SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();
|
||||
params.setSkusList(skuList).setType(BillingClient.SkuType.SUBS);
|
||||
billingClient.querySkuDetailsAsync(params.build(), (billingResult, skuDetailsList) -> {
|
||||
// Process the result.
|
||||
Log.d(Premium.this.getLocalClassName(), "SkuDetailsResponse - result message: " + billingResult.getDebugMessage() + " | result response code: " + billingResult.getResponseCode());
|
||||
if (skuDetailsList != null) {
|
||||
for (SkuDetails skuDetails : skuDetailsList) {
|
||||
Log.d(Premium.this.getLocalClassName(), "Sku detail: " + skuDetails.getTitle() + " | " + skuDetails.getDescription() + " | " + skuDetails.getPrice() + " | " + skuDetails.getSku());
|
||||
}
|
||||
} else {
|
||||
Log.d(Premium.this.getLocalClassName(), "Empty sku list");
|
||||
}
|
||||
Log.d(Premium.this.getLocalClassName(), "SkuDetailsResponse");
|
||||
processSkuDetailsList(skuDetailsList);
|
||||
});
|
||||
}
|
||||
|
||||
private void processSkuDetailsList(@Nullable List<SkuDetails> skuDetailsList) {
|
||||
if (skuDetailsList != null) {
|
||||
for (SkuDetails skuDetails : skuDetailsList) {
|
||||
if (skuDetails.getSku().equals(subscriptionSku)) {
|
||||
Log.d(Premium.this.getLocalClassName(), "Sku detail: " + skuDetails.getTitle() + " | " + skuDetails.getDescription() + " | " + skuDetails.getPrice() + " | " + skuDetails.getSku());
|
||||
subscriptionDetails = skuDetails;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (subscriptionDetails != null) {
|
||||
showSubscriptionDetails();
|
||||
} else {
|
||||
showSubscriptionDetailsError();
|
||||
}
|
||||
}
|
||||
|
||||
private void showSubscriptionDetailsError() {
|
||||
binding.textLoading.setText(R.string.premium_subscription_details_error);
|
||||
binding.textLoading.setVisibility(View.VISIBLE);
|
||||
binding.containerSub.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
private void showSubscriptionDetails() {
|
||||
// handling dynamic currency and pricing for 1Y subscriptions
|
||||
String currencySymbol = subscriptionDetails.getPrice().substring(0, 1);
|
||||
String priceString = subscriptionDetails.getPrice().substring(1);
|
||||
double price = Double.parseDouble(priceString);
|
||||
StringBuilder pricingText = new StringBuilder();
|
||||
pricingText.append(subscriptionDetails.getPrice());
|
||||
pricingText.append(" per year (");
|
||||
pricingText.append(currencySymbol);
|
||||
pricingText.append(String.format(Locale.getDefault(), "%.2f", price / 12));
|
||||
pricingText.append("/month)");
|
||||
|
||||
binding.textSubTitle.setText(subscriptionDetails.getTitle());
|
||||
binding.textSubPrice.setText(pricingText);
|
||||
binding.textLoading.setVisibility(View.GONE);
|
||||
binding.containerSub.setVisibility(View.VISIBLE);
|
||||
binding.containerSub.setOnClickListener(view -> launchBillingFlow(subscriptionDetails));
|
||||
}
|
||||
|
||||
private void launchBillingFlow(@NonNull SkuDetails skuDetails) {
|
||||
Log.d(Premium.this.getLocalClassName(), "launchBillingFlow");
|
||||
// Retrieve a value for "skuDetails" by calling querySkuDetailsAsync().
|
||||
Log.d(Premium.this.getLocalClassName(), "launchBillingFlow for sku: " + skuDetails.getSku());
|
||||
BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder()
|
||||
.setSkuDetails(skuDetails)
|
||||
.build();
|
||||
int responseCode = billingClient.launchBillingFlow(this, billingFlowParams).getResponseCode();
|
||||
billingClient.launchBillingFlow(this, billingFlowParams);
|
||||
}
|
||||
|
||||
// Handle the result.
|
||||
|
||||
private void handlePurchase(Purchase purchase) {
|
||||
Log.d(Premium.this.getLocalClassName(), "handlePurchase: " + purchase.getOrderId());
|
||||
if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED && !purchase.isAcknowledged()) {
|
||||
Log.d(Premium.this.getLocalClassName(), "acknowledge purchase: " + purchase.getOrderId());
|
||||
AcknowledgePurchaseParams acknowledgePurchaseParams =
|
||||
AcknowledgePurchaseParams.newBuilder()
|
||||
.setPurchaseToken(purchase.getPurchaseToken())
|
||||
.build();
|
||||
billingClient.acknowledgePurchase(acknowledgePurchaseParams, acknowledgePurchaseResponseListener);
|
||||
}
|
||||
}
|
||||
|
||||
private void enablePremiumAccess() {
|
||||
// what now?
|
||||
}
|
||||
}
|
||||
|
|
|
@ -126,7 +126,7 @@ public class StoryViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
|
|||
}
|
||||
|
||||
public int getStoryCount() {
|
||||
if (fs != null && !UIUtils.hasPremiumAccess(context, fs)) {
|
||||
if (fs != null && UIUtils.needsPremiumAccess(context, fs)) {
|
||||
return Math.min(3, stories.size());
|
||||
} else {
|
||||
return stories.size();
|
||||
|
|
|
@ -307,7 +307,7 @@ public class ItemSetFragment extends NbFragment implements LoaderManager.LoaderC
|
|||
return;
|
||||
}
|
||||
|
||||
if (cursorSeenYet && adapter.getRawStoryCount() > 0 && !UIUtils.hasPremiumAccess(requireContext(), getFeedSet())) {
|
||||
if (cursorSeenYet && adapter.getRawStoryCount() > 0 && UIUtils.needsPremiumAccess(requireContext(), getFeedSet())) {
|
||||
fleuronBinding.getRoot().setVisibility(View.VISIBLE);
|
||||
fleuronBinding.containerSubscribe.setVisibility(View.VISIBLE);
|
||||
binding.topLoadingThrob.setVisibility(View.INVISIBLE);
|
||||
|
@ -428,7 +428,7 @@ public class ItemSetFragment extends NbFragment implements LoaderManager.LoaderC
|
|||
if (dy < 1) return;
|
||||
|
||||
// skip fetching more stories if premium access is required
|
||||
if (!UIUtils.hasPremiumAccess(requireContext(), getFeedSet()) && adapter.getItemCount() >= 3) return;
|
||||
if (UIUtils.needsPremiumAccess(requireContext(), getFeedSet()) && adapter.getItemCount() >= 3) return;
|
||||
|
||||
ensureSufficientStories();
|
||||
|
||||
|
|
|
@ -582,12 +582,11 @@ public class UIUtils {
|
|||
}
|
||||
}
|
||||
|
||||
public static boolean hasPremiumAccess(Context context, FeedSet feedSet) {
|
||||
public static boolean needsPremiumAccess(Context context, FeedSet feedSet) {
|
||||
boolean isPremium = PrefsUtils.isPremium(context);
|
||||
boolean requiresPremium = feedSet.isFolder() || feedSet.isInfrequent() ||
|
||||
feedSet.isAllNormal() || feedSet.isGlobalShared() || feedSet.isSingleSavedTag();
|
||||
//TODO: change flag
|
||||
return !(isPremium && requiresPremium);
|
||||
return !isPremium && requiresPremium;
|
||||
}
|
||||
|
||||
public static void startPremiumActivity(Context context) {
|
||||
|
|
Loading…
Add table
Reference in a new issue